Android HttpsURLConnection, how to tell if the response is cached? - android

I've got an app targeting ICS and above, and I'm trying to use HttpsURLConnection and HttpResponseCache to cache a web service response. It makes the request with an etag, and the server will return 304 if not modified. It appears to be returning stuff from the cache, however, I'm not receiving any indication that the response is from cache. The response code I'm getting from connection.getResponseCode() is 200, not 304. I need to know if it's cached because I want to know if it's changed so I can mark it as read (it's a fairly long string, and I would not like to have to compare the two). Is there something I can set or something I can check so I know that it is from the cache instead of freshly fetched?

You can try using the response header information. Compare the dates, if you are getting the same date for the same request, then it's the cache.
HttpURLConnection conn;
conn = (HttpURLConnection)url.openConnection();
...
conn.connect();
System.out.println(conn.getHeaderField("Date"));
if you want to print the whole header:
Map headerfields = conn.getHeaderFields();
Set headers = headerfields.entrySet();
for(Iterator i = headers.iterator(); i.hasNext();){
Map.Entry map = (Map.Entry)i.next();
System.out.println(map.getKey() + " : " + map.getValue() + "");
}

After print the whole response header on Android 9. I found the following fields useful.
HttpURLConnection conn;
...
conn.getHeaderField("X-Android-Response-Source"); // "NETWORK 200" or "CACHE 200"
conn.getHeaderField("Warning"); // null or "110 HttpURLConnection "Response is stale""
It should have other status messages that I have not tested.

Related

Sending HttpURLConnection inputStream to WebResourceResponse not resulting in proper response for WebView

I'm trying to append the cors headers to a request made in an Android webview by a JavaScript fetch command in order to bypass cors. To do this I'm intercepting requests made in the WebView with the shouldInterceptRequest member function.
The code I have seems to be working except that the stream I'm getting from the HttpURLConnection doesn't seem to work with the stream required by the WebResourceResponse. The relevant code (inside shouldInterceptRequest function):
val url = URL("https://www.android.com")
val urlConnection: HttpURLConnection = url.openConnection() as HttpURLConnection
try {
var headers =
urlConnection.headerFields.mapValues { it.value.joinToString() }
headers = headers.toMutableMap()
headers["Access-Control-Allow-Origin"] = "*"
headers["Access-Control-Allow-Headers"] = "*"
headers["Access-Control-Allow-Credentials"] = "true"
// val inStream: InputStream = BufferedInputStream(urlConnection.inputStream) // Doesn't work
// val string = "hello world :)"
// val inStream = string.byteInputStream(charset = Charset.forName("UTF-8")) // Works when used as stream, sends "hello world :)" as response
return WebResourceResponse(
headers["Content-Type"],
if (urlConnection.contentEncoding === null) "UTF-8" else urlConnection.contentEncoding,
urlConnection.responseCode,
"OK",
headers,
urlConnection.inputStream
)
} finally {
urlConnection.disconnect()
}
The last argument of the WebResourceResponse needs to be a stream which will be sent to the Webview page. When using the urlConnection.inputStream I can see in Chrome devtools that the headers are set properly on the response, but the data fails to load.
When I replace the response stream with a string the solution works fine, and the webview receives the string correctly. My question is then why doesn't the stream from the HttpURLConnection work when given to the response object, and how do I make it work.
The solution I'm looking for should not only support text, but also binary data and streams.
As a side note I've also tried implementing the requestIntercept asynchronously, as mentioned here: How to get WebViewClient.shouldInterceptRequest invoked asynchronously, which changes the stream given to the response object, however this resulted in a very slow request followed by an android crash after the data had loaded and the request was done.
This isn't a duplicate of WebResourceResponse can't read full inputstream from HttpConnection (Android), the problem is different and the solution wouldn't work.
Entire code file: https://gist.github.com/RuurdBijlsma/2b52d80a4d74460ac2837ee55b0b933c in case that's helpful.

JsonWriter POST not working in Android to WCF web service

I would like to know anyone has a sample code on how to use JsonWriter to post JSON data to WCF web service from Android?
I tested my WCF with Fiddler 4 (Composer with POST json data) and it gave me the correct return.
However, when I tested with my Android application which use JsonWriter, I didn't see any action on Fiddler (I set up Fiddler to check on my Android Emulator network traffic, by the way, I am testing on Android Emulator.).
With the same Android application, I can call GET with JsonReader to my WCF and get the correct reply.
Its just calling POST with JsonWriter got no response code or no action in Fiddler.
For JsonWriter (and Reader), I refer to Android developer >> JsonWriter
Here are my test results (Get and Post) with Emulator GET and POST.
Here are my test results with Fiddler direct POST.
First it gave me Result 307 then follow by 200.
And here is how I use JsonWriter to post (this block was from AsyncTask).
try
{
Log.d("TEST_JSON", "URL: " + params[0]);
URL url = new URL(params[0]);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept","application/json");
conn.setRequestProperty("Content-Type","application/json");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setAllowUserInteraction(false);
// conn.connect();
OutputStream out = conn.getOutputStream();
JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
try
{
writer.setIndent(" ");
if(params[1].trim() == "ARRAY")
{
// Write array to WCF.
}
else if(params[1].trim() == "OBJ")
{
// Write object to WCF. <<== I am testing with one object.
writer.beginObject();
writer.name("ShipNo").value("SI10101");
writer.name("DoNo").value("DO230401");
writer.name("PartNo").value("102931-1201");
writer.name("Qty").value(1);
writer.name("ShipIn").value(1);
writer.endObject();
}
}
finally
{
writer.close();
out.close();
}
// If I enable below blocks, I will see 307 response code in Fiddler.
/*
conn.connect();
int responseCode = conn.getResponseCode();
Log.d("TEST_JSON", "Code: " + String.valueOf(responseCode));
*/
Log.d("TEST_JSON", "Finish sending JSON.");
conn.disconnect();
}
catch (IOException e)
{
Log.e("TEST_JSON",e.getMessage()); // <<-- No error from this try catch block.
}
I tried and still cannot figure out why JsonWriter didn't trigger to my WCF (I attached my WCF to my localhost service, only Fiddler direct POST will hit the break point in my WCF project while Android App didn't reach to it). I follow the exact example from Android Developer site though. I google and didn't find any site on using JsonWriter with OutputStreamWriter (I saw some post using StringWriter).
May I know where did my code wrong ?
Based on this StackOverFlow post WCF has a 'Thing' about URI, I managed to solve this issue.
All I need is to make sure my POST web service has URI Template ends with "Slash".
Example: http://10.72.137.98/myWebSvc/posvctFun/
Instead of http://10.72.137.98/myWebSvc/postFun

getResponseCode weather times out nor reads response

I have an android application which can upload some json data to a PHP script which stores the data in a database.
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
connection.setDoOutput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("PUT");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("charset", "utf-8");
connection.setFixedLengthStreamingMode(bytes.length);
connection.setReadTimeout(3000000);
long tim = System.currentTimeMillis();
connection.getOutputStream().write(bytes);
connection.getOutputStream().flush();
if (connection.getResponseCode() != 200) {
long dur = System.currentTimeMillis() - tim;
String response = IOUtils.toString(connection.getInputStream(), "UTF-8");
throw new IOException(connection.getResponseCode() + " " + response);
}
} finally {
connection.disconnect();
}
The data arrives at the script and the scripts stores the data into the database.
When the client uploads a huge amount of data the request takes about 15 minutes, though it completes. The data is completly written into the database and the php script sends it's response.
But when it takes that long the getResponseCode() function never returns. When i hit pause in the debugger it shows me the following stack:
Any idea what's running wrong?
I'm running an emulator on API level 23.
It seems that the connection gets lost but android does not detect it.

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.

Android keeping connection alive to receive constant data update from server

i hope you can help me with my problem. I have to instantiate an http connection and do a get call to the server.
After this call i have to check periodically if the server sent me some data (the connection must not be closed after the first data received). If it does, then i have to parse this data and pass it to an activity and wait again.
My problem is to understand if i'm doing correctly this thing. Here my code
try {
URL url = getUrl();
URLConnection urlConn = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) urlConn;
httpConnection.setRequestMethod("GET");
httpConnection.connect();
while(true) {
sleep(5000);
int responseCode = httpConnection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpConnection.getInputStream();
String response = null;
if(inputStream.available() > 0) {
long length = inputStream.available();
byte[] bytes = new byte[(int) length];
inputStream.read(bytes);
response = new String(bytes, "UTF-8");
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
getUrl() function gives me the complete url that i have to call.
Then i connect through httpConnection.connect();.
while i'm iterating is the connection already opend and capable of receiving incoming data?
I apologize for my english. Thanks a lot
Francesco
From the official android documentation
Uses of this class follow a pattern:
Obtain a new HttpURLConnection by calling URL.openConnection() and
casting the result to HttpURLConnection.
Prepare the request. The
primary property of a request is its URI. Request headers may also
include metadata such as credentials, preferred content types, and
session cookies.
Optionally upload a request body. Instances must be
configured with setDoOutput(true) if they include a request body.
Transmit data by writing to the stream returned by getOutputStream().
Read the response. Response headers typically include metadata such as
the response body's content type and length, modified dates and
session cookies. The response body may be read from the stream
returned by getInputStream(). If the response has no body, that method
returns an empty stream.
Disconnect. Once the response body has been
read, the HttpURLConnection should be closed by calling disconnect().
Disconnecting releases the resources held by a connection so they may
be closed or reused.
As you see from this quote, if you need to send some data to a host you just need to call setDoOutput(true) and use OutputStream of your connection to send data to the host. In this case you use "POST" request method after you called setDoOutput(true). If you don't need to send data, but just to connect to the host and retrieve data, you also don't need to call setRequestMethod("GET"), because it's default request method.
When you connect to the host, just use InputStream to get data from it.

Categories

Resources