Volley Cache returns null when receiving 304 - android

I am trying to get Volley to work using its Cache. When I receive a 304 getCacheEntry().data is null even though "cache-control" is set. Here is what I am doing:
Volley gets instantiated likes this
// Instantiate the cache
Cache cache = new DiskBasedCache(c.getCacheDir(), 10 * 1024 * 1024); // 10 MB cap
// Set up the network to use HttpURLConnection as the HTTP client.
Network network = new BasicNetwork(new HurlStack());
// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(cache, network);
// Start the queue
mRequestQueue.start();
After sending a GET request I get the response 200 with "cache-control" set to "max-age=180, public". So far so good right?
If a GET request gets made twice or more I set "If-Modified-Since" with the last timestamp the request was made to the request header.
The second time I request a specific API endpoint the sever will respond with a 304. getCacheEntry().data returns null though. If I check the cache entries in Volleys RequestQueue I cannot find an entry for my specific request.
What am I doing wrong? For some reason I have one request that is always cached when fired once. It's even one that returns a lot of data. But all other requests aren't cached. Following code snippet parses the response and checks for 304.
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response, String charset) {
try {
String json = "";
if (!response.notModified) {
json = new String(response.data, charset);
} else {
//if not modified -> strangely getCacheEntry().data always null
json = new String(getCacheEntry().data, charset);
}
return Response.success(
gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
I really appreciate any comments on this.

IMHO, your cache entry is always null (not only when getting 304 resp code), because of the following:
Cache cache = new DiskBasedCache(c.getCacheDir(), 10 * 1024 * 1024); // 10 MB cap
Please check your c.getCacheDir() to see if you want to use External Storage to store cache data, then you should set WRITE_EXTERNAL_STORAGE permission inside AndroidManifest.xml file.
Hope this helps!

Related

Serve cache on timeout in okhttp3

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);
}
}

Volley parseNetworkResponse is not called if last response body is null

Problem:
Once I execute DELETE request using Volley, the very next request always goes to onErrorResponse block (bypassing parseNetworkResponse). After that all requests work fine until again I execute DELETE request. The very next request doesn't work. If I try the same request again, it will work.
Description:
I'm using JsonObjectRequest to fire all APIs. As for DELETE request, response body is null. Thus, JsonObjectRequest can't handle it and results into Parse error. So, I implemented parseNetworkResponse to explicitly check for null body. Now, DELETE request works.
Here is my code,
#Override
protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
//Allow null
if (jsonString == null || jsonString.length() == 0) {
jsonString = "{'status':'success'}";
}
return Response.success(new JSONObject(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je) {
return Response.error(new ParseError(je));
}
}
Above snippet allowed JsonObjectRequest to handle null body. Now, DELETE request is working fine.
But as soon as I fire DELETE request, the very next request stops
working. It directly goes to onErrorResponse and
parseNetworkResponse is not being called.
Example:
Fired multiple POST, GET combinations. All good.
Fired DELETE request. Successfully completed
Fired one POST or GET request. Directly goes to onErrorResponse (bypassing parseNetworkResponse).
Again tried the same request, it worked.
Everything works properly until again I fire DELETE request. Once I fire DELETE request, then the very next request only doesn't work.
It throws below error,
com.android.volley.NoConnectionError: java.net.ProtocolException: Unexpected status line: <link rel='stylesheet' type='text/css' property='stylesheet' href='//MY_DOMAIN_NAME/_debugbar/assets/stylesheets?v=1484813989'><script type='text/javascript' src='//MY_DOMAIN_NAME/_debugbar/assets/javascript?v=1483605979'></script><script type="text/javascript">jQuery.noConflict(true);</script>
I also tried to disable the cache for all requests. I even tried appending random number as a parameter to make sure url is always unique. Still it didn't work.
Edit:
Also further debugging shows that, parseNetworkError is called. Apache log on backend suggests that sometimes that faulty request reaches to the server and server returned correct response though it doesn't go to parseNetworkResponse and shows error.
Majority of the times, request doesn't even reach to the server at all.
It sounds very weird bug. Any help will be greatly appreciated. Let me know if you need more information.

Enabling cache in sending http requests(Android, JAVA)

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 !!

volley - json caching is not working properly

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.

FileNotFoundException when using the offline cache of HttpResponsecache

I'm using HttpResponseCache to enable the response caching (for web requests) in my android app, and the offline cache isn't working.
I'm doing the offline cache as the documentation tells me to do.
In my Application class, at the onCreate method, I'm turning on the the cache with:
try {
long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
File httpCacheDir = new File(getCacheDir(), "http");
Class.forName("android.net.http.HttpResponseCache")
.getMethod("install", File.class, long.class)
.invoke(null, httpCacheDir, httpCacheSize);
} catch (Exception httpResponseCacheNotAvailable) {}
At my HttpConnection class I'm getting the JSON with the method:
private String sendHttpGet(boolean cacheOnly) throws Exception {
URL url = new URL(getUrlCompleta());
HttpURLConnection urlConnection = null;
String retorno = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
if(urlConnection == null)
throw new Exception("Conn obj is null");
fillHeaders(urlConnection, cacheOnly);
InputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8192);
retorno = convertStream(in);
in.close();
urlConnection.disconnect();
if(retorno != null)
return retorno;
} catch(IOException e) {
throw e;
} finally {
if(urlConnection != null)
urlConnection.disconnect();
}
throw new Exception();
}
Where the convertStream method just parse a InputStream into a String.
The method fillHeaders put an token on the request (for security reasons) and if the parameter cacheOnly is true, then the header "Cache-Control", "only-if-cached" is added to the request header ( with the code: connection.addRequestProperty("Cache-Control", "only-if-cached");)
The cache works 'fine' (with minor strange behaviors) when there is connectivity and the app hit the web server just to see if there is a newer version of the JSON. When the web server answers "nothing changed", the cache works.
The problem is when I have no connectivity and use the header "Cache-Control", "only-if-cached". In this case, I receive a java.io.FileNotFoundException: https://api.example.com/movies.json. That is awkward, because the implementation code of the cache probably stores the response in a file named using a hash function on the request url, and not the url itself.
Does anyone knows what can I do or what is wrong with my implementation?
ps: Above, I said "probably using a hash function", because I was not able to found the implementation of the com.android.okhttp.HttpResponseCache object (the class that android.net.http.HttpResponseCache delegates cache calls). If someone found it, please tell me where to look at :)
ps2: Even when I add a max-stale parameter in the Cache-Control header, it still doesn't work.
ps3: I obviously tested it on api 14+.
ps4: Although I'm accessing an "https://" URL address, the same behavior occurs when the URL is just a normal "http://" address.
It turns out that the problem was with the max-age value of the Cache-control directive in the response given by my web server. It had the following value: Cache-Control: max-age=0, private, must-revalidate. With this directive, my server was saying to the cache that the response could be used from the cache even if it was 0 seconds old. So, my connection wasn't using any cached response.
Knowing that max-age is specified in seconds, all I had to do was change the value to: Cache-Control: max-age=600, private, must-revalidate! There it is, now I have a 10 minute cache.
Edit: If you want to use a stale response, with the max-stale directive of the request, you shouldn't use the must-revalidate directive in the response, as I did in my webserver.

Categories

Resources