IP fallback in android - java

I'm accessing a server for web service calls. When I'm developing on the same network as the server, I can access the web service by its internal IP address but not its external IP address. However, if I'm not on the network, I can only access it by its external IP address. What's the best way to try one of the IP addresses and then fall back on the other?
Here's a sample of my code for accessing only one or the other:
protected String retrieve() {
Log.v(TAG, "retrieving data from url: " + getURL());
HttpPost request = new HttpPost(getURL());
try {
StringEntity body = new StringEntity(getBody());
body.setContentType(APPLICATION_XML_CONTENT_TYPE);
request.setEntity(body);
HttpConnectionParams.setConnectionTimeout(client.getParams(), CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(client.getParams(), SOCKET_TIMEOUT);
HttpResponse response = client.execute(request);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.e(TAG, "the URL " + getURL() + " returned the status code: " + statusCode + ".");
return null;
}
HttpEntity getResponseEntity = response.getEntity();
if (getResponseEntity != null) {
return EntityUtils.toString(getResponseEntity);
}
} catch (IOException e) {
Log.e(TAG, "error retrieving data.", e);
request.abort();
}
return null;
}
/*
* #return the URL which should be called.
*/
protected String getURL() {
return INTERNAL_SERVER_URL + WEB_APP_PATH;
}

Look at own IP address of your android. You can get it like stated here.
Then you can decide:
if you are in subnet of your office (e.g. 192.168.0.0/16) - use internal address
if you are in other subnet - use external address

Building on the very good comment by harism, I would simply use a static boolean to choose the IP and thus avoid pinging the wrong IP every time:
public static final Boolean IS_DEBUG = true; // or false
// all your code here
if (DEBUG)
ip = xxx.xxx.xxx.xxx;
else
ip = yyy.yyy.yyy.yyy;

This isn't exactly something you can easily fix in software. The right answer I think is fixing the filters/configuration that route traffic to your internal web server or by properly configuring DNS to return the proper IP depending on where you are (inside or outside the network). More information can be found here:
Accessing internal network resource using external IP address
http://www.astaro.org/astaro-gateway-products/network-security-firewall-nat-qos-ips-more/6704-cant-acces-internal-webserver-via-external-ip.html
and by Googling something like "external IP doesn't work on internal network"

You could put retry code in the catch clause for IOException
protected String retrieve(String url) {
Log.v(TAG, "retrieving data from url: " + url);
HttpPost request = new HttpPost(url);
try {
StringEntity body = new StringEntity(getBody());
body.setContentType(APPLICATION_XML_CONTENT_TYPE);
request.setEntity(body);
HttpConnectionParams.setConnectionTimeout(client.getParams(), CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(client.getParams(), SOCKET_TIMEOUT);
HttpResponse response = client.execute(request);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.e(TAG, "the URL " + getURL() + " returned the status code: " + statusCode + ".");
return null;
}
HttpEntity getResponseEntity = response.getEntity();
if (getResponseEntity != null) {
return EntityUtils.toString(getResponseEntity);
}
} catch (IOException e) {
if(url.equals(EXTERNAL_URL){
return retrieve(INTERNAL_URL);
}
Log.e(TAG, "error retrieving data.", e);
request.abort();
}
return null;
}
Note: Like most people have said, this probably is not a great solution for a production release, but for testing it would probably work just fine.

You could change your retrieve() call to take the host as a parameter. On startup, try to ping each possible host. Do a simple retrieve call to something that returns very fast (like maybe a test page). Work through each possible host you want to try. Once you found one that works, save that host and use it for all future calls.

1st, you should be able to handle the case on the runtime. I'd very strongly recommend vs the different builds.. For example: try the external, if fails the internal.
Some heuristics meanwhile:
Internal IP implies the network is the same. So you can check it, if it's the same try the internal address 1st, otherwise the external has precedence.
Later,save the mapping of the local ip address to the successfully connected one and look it up to alter the precedence.
The resolution itself may be carried by requesting the root '/' of the server with a timeout (you can use 2 different threads to carry the task simultaneously, if you feel like it).
Morealso, if you have access to the router/firewall it can be made to recognize the external addresses and properly handle it. So you can end up with the external address that works properly.

I would put this kind of environment-specific information in a properties file (or some kind of configuration file anyway). All you need is a file with one line (obviously you would change the IP address to what you need):
serverUrl=192.168.1.1
Java already has a built-in feature for reading and writing these kinds of files (see the link above). You could also keep database connection information etc. in this file. Anything environment-specific really.
In your code it looks like you are using constants to hold the server URL. I would not suggest that. What happens when the server URL changes? You'd need to modify and re-compile your code. With a configuration file, however, no code changes would be necessary.

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
and:
HttpClient client = new HttpClient();
HttpMethod method = new GetMethod("http://www.myinnersite.com");
int responseCode = client.executeMethod(method);
if (responseCode != 200) {
method = new GetMethod("http://www.myoutersite.com");
}
etc...

Related

Authenticate with CouchDB and insert new database via HTTP PUT in Android app

I am implementing an Android app that should upload data to CouchDB. Since I have restricted the admin access to one account, I have to authenticate before inserting a new database. And this is what I am currently struggling with: Authenticate and insert a new database. Operating via Terminal and using curl, everything is working out fine the following way:
> curl -X PUT http://admin_name:admin_password#url:port/database_to_be_inserted
First approach
My first approach was to simply do the same via HTTP PUT in my code like that:
private boolean putJSON(String json, String url) {
// url = http://admin_name:admin_password#url:port/database_to_be_inserted
HttpClient client = new DefaultHttpClient();
HttpPut put = new HttpPut(url);
try {
StringEntity stringEntity = new StringEntity(json,"utf-8");
put.setEntity(stringEntity);
put.setHeader("Content-type", "application/json; charset=utf-8");
put.setHeader("Accept", "application/json");
HttpResponse response = client.execute(put);
// ... buffered input reading on response...
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
However, doing so I retrieve the following error and JSON array:
Authentication error: Unable to respond to any of these challenges: {}
{"error":"unauthorized","reason":"You are not a server admin."}
The point is, that using the same method for inserting a new user works out perfectly. So, if I am using the above method with a correctly formatted user JSON-Dictionary and the following url, the user is inserted correctly.
http://admin_name:admin_password#url:port/_users/org.couchdb.user:user_name
This should prove, that I am using the right admin data at least, shouldn't it?
Second approach
So, by now, I am trying to authenticate using the "Authorization" option in my HTTP PUT's header:
private boolean putDatabase(String userName, String password, String url) {
// url = "http://url:port/database_to_be_inserted"
HttpClient client = new DefaultHttpClient();
HttpPut put = new HttpPut(url);
String authenticationData = userName+":"+password;
String encoding = Base64.encodeToString(authenticationData.getBytes(Charset.forName("utf-8")), Base64.DEFAULT);
put.setHeader("Authorization", "Basic " + encoding);
try {
put.setHeader("Content-type", "application/json; charset=utf-8");
put.setHeader("Accept", "application/json");
HttpResponse response = client.execute(put);
// ... buffered input reading on response...
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
Still no success in inserting the database. The response I am parsing says:
Host not found
I have double checked the admin name, password, and url and everything seems correct. Does anyone of you see why this might not work out?
Ok, the answer is simple: The above code (at least the one of my second approach) is working fine. My mistake was to not explicitly specify the port via which the CouchDB should be accessed. This is, how I accidentally called the method:
putDatabase("adminName", "adminPassword", "http://url/database_to_be_inserted");
However, this is how I should have called it:
putDatabase("adminName", "adminPassword", "http://url:port/database_to_be_inserted");
Who is using iriscouch like me and does not know which port to specify here, can look it up in the config file. Using Futon this can be found in the entry "httpd > port" here:
> http://your_url_spec.iriscouch.com/_utils/config.html
More general and without Futon this can be found (and if you wish so edited) via command-line in the local.ini of your own CouchDB installation:
~$ cat etc/couchdb/local.ini

Apache Jackrabbit webDAV PUT Method.

beginning to work with Jackrabbit 2.6.3 and commons httpclient 3.
I've written a simple webDav client to upload a file to a server and want to test it against jackrabbit's standalone server's default repository. Simply I just want to put a file in the default repository.
my httpclient connects and the method begins buffering my file. Trouble is, I can't seem to work what URL I should point my http method at to correctly put it in the repository. The standalone server is running on:
http://localhost:8080.
I seem to either get a 405 PUT not supported or 404 or 403 or the even more curious "repository '/' does not begin with '/default' for all the urls I've tried. I can see the default repository content if I point my browser at:
http://localhost:8080/repository/default/
Simply, my question is, what is the url to do this with a PutMethod? As rudimentary as that sounds.
I've included some truncated code for the class I've written, specifically the method I'm working with at the moment, I think it should be enough to show my approach is correct.
public void insertFile(byte[] content, String id) throws Exception {
PutMethod httpMethod = new PutMethod("http://localhost:8080/repository/default/");
InputStream is = new ByteArrayInputStream(content);
FileMetaData meta = new FileMetaData();
meta.address = destUri;
meta.id = id;
meta.mimeType = Files.probeContentType(Paths.get(meta.address));
RequestEntity requestEntity = new InputStreamRequestEntity(is, meta.mimeType);
httpMethod.setRequestEntity(requestEntity);
try {
int statusCode = client.executeMethod(httpMethod);
if (statusCode != HttpStatus.SC_OK) System.err.println("Method failed: " + httpMethod.getStatusLine());
byte[] responseBody = httpMethod.getResponseBody();
System.out.println(new String(responseBody));
} catch (HttpException e) {
System.err.println("Fatal protocol violation: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("Fatal transport error: " + e.getMessage());
e.printStackTrace();
} finally {
httpMethod.releaseConnection();
}
}
I'm sure it's a simple answer, but trawling the docs don't seem to show up any resources or tutorials relating to this. Any help appreciated.
Really silly, totally thought that the requestEntity would send some data on file and use that as a file name, instead I have to specify that myself. PROTIP: Step away from the computer for 5 - 10 minutes. For anyone else with my issue it should be '"yourUrl/repository/default" + fileName'. Easy.

Java - Quickest way to check if URL exists

Hi I am writing a program that goes through many different URLs and just checks if they exist or not. I am basically checking if the error code returned is 404 or not. However as I am checking over 1000 URLs, I want to be able to do this very quickly. The following is my code, I was wondering how I can modify it to work quickly (if possible):
final URL url = new URL("http://www.example.com");
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
int responseCode = huc.getResponseCode();
if (responseCode != 404) {
System.out.println("GOOD");
} else {
System.out.println("BAD");
}
Would it be quicker to use JSoup?
I am aware some sites give the code 200 and have their own error page, however I know the links that I am checking dont do this, so this is not needed.
Try sending a "HEAD" request instead of get request. That should be faster since the response body is not downloaded.
huc.setRequestMethod("HEAD");
Again instead of checking if response status is not 400, check if it is 200. That is check for positive instead of negative. 404,403,402.. all 40x statuses are nearly equivalent to invalid non-existant url.
You may make use of multi-threading to make it even faster.
Try to ask the next DNS Server
class DNSLookup
{
public static void main(String args[])
{
String host = "stackoverflow.com";
try
{
InetAddress inetAddress = InetAddress.getByName(host);
// show the Internet Address as name/address
System.out.println(inetAddress.getHostName() + " " + inetAddress.getHostAddress());
}
catch (UnknownHostException exception)
{
System.err.println("ERROR: Cannot access '" + host + "'");
}
catch (NamingException exception)
{
System.err.println("ERROR: No DNS record for '" + host + "'");
exception.printStackTrace();
}
}
}
Seems you can set the timeout property, make sure it is acceptable. And if you have many urls to test, do them parallelly, it will be much faster. Hope this will be helpful.

Bing Maps REST services returns incorrect characters for French. Is there a way to request them to encode their response or decode it somehow?

In the Android application I have written, there is a portion which allows the user to enter the start and end location of their trip, and a route itinerary is returned. I am using Bing Maps REST services for this. I want the directions returned to be in French.
A sample request: request. This is best seen on a Chrome browser, Safari and Firefox take care of this. You can see that the directions have lots of strange characters where they are not supposed to be. I have tried decoding on the device, by doing:
URLDecoder.decode(obj.optString("text"), HTTP.ISO_8859_1)
which does not work (the response stays the same), which makes sense I think since it has already become the special characters. I cannot use Windows-1252 to decode because Android does not seem to support that.
An example of what I am being sent back: Léger Encombrement. What it should be: Léger Encombrement.
It works perfectly on an iPhone as well, but not on Android. Any suggestions on how I can solve this?
My code in the connection class is:
public static JSONObject getJSONResult(final String url) {
try {
final HttpClient client = new DefaultHttpClient();
final HttpGet get = new HttpGet(url);
final HttpResponse responsePost = client.execute(get);
final HttpEntity resEntity = responsePost.getEntity();
boolean DEBUG = true;
if (DEBUG) {
Log.d("", "[JSON-ENV] url: " + url);
}
final String str = EntityUtils.toString(resEntity);
Log.d("connection", "response str: " + str);
if (resEntity != null) {
final JSONObject obj = new JSONObject(str);
Log.d("connection", "JSON RESPONSE IS " + obj);
return obj;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
Is there something I need to add into my connection class?
UPDATE:
I added the JSON parsing code to format as "ISO_8859_1" as seen at this link: http://p-xr.com/android-tutorial-how-to-parse-read-json-data-into-a-android-listview/ but I still get the same results ...
This is JSON. You don't need to use URLDecoder. The error is before that, probably when you create the String for the JSON Parser. JSON is always in UTF-8 (or 16, rarely)
Can you post the code for reading the server response?
edit
EntityUtils uses ISO_8859_1 as a default Charset if it does not find one in the content. Simply change
final String str = EntityUtils.toString(resEntity);
to
final String str = EntityUtils.toString(resEntity, HTTP.UTF_8);

Preferred Java way to ping an HTTP URL for availability

I need a monitor class that regularly checks whether a given HTTP URL is available. I can take care of the "regularly" part using the Spring TaskExecutor abstraction, so that's not the topic here. The question is: What is the preferred way to ping a URL in java?
Here is my current code as a starting point:
try {
final URLConnection connection = new URL(url).openConnection();
connection.connect();
LOG.info("Service " + url + " available, yeah!");
available = true;
} catch (final MalformedURLException e) {
throw new IllegalStateException("Bad URL: " + url, e);
} catch (final IOException e) {
LOG.info("Service " + url + " unavailable, oh no!", e);
available = false;
}
Is this any good at all (will it do what I want)?
Do I have to somehow close the connection?
I suppose this is a GET request. Is there a way to send HEAD instead?
Is this any good at all (will it do what I want?)
You can do so. Another feasible way is using java.net.Socket.
public static boolean pingHost(String host, int port, int timeout) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), timeout);
return true;
} catch (IOException e) {
return false; // Either timeout or unreachable or failed DNS lookup.
}
}
There's also the InetAddress#isReachable():
boolean reachable = InetAddress.getByName(hostname).isReachable();
This however doesn't explicitly test port 80. You risk to get false negatives due to a Firewall blocking other ports.
Do I have to somehow close the connection?
No, you don't explicitly need. It's handled and pooled under the hoods.
I suppose this is a GET request. Is there a way to send HEAD instead?
You can cast the obtained URLConnection to HttpURLConnection and then use setRequestMethod() to set the request method. However, you need to take into account that some poor webapps or homegrown servers may return HTTP 405 error for a HEAD (i.e. not available, not implemented, not allowed) while a GET works perfectly fine. Using GET is more reliable in case you intend to verify links/resources not domains/hosts.
Testing the server for availability is not enough in my case, I need to test the URL (the webapp may not be deployed)
Indeed, connecting a host only informs if the host is available, not if the content is available. It can as good happen that a webserver has started without problems, but the webapp failed to deploy during server's start. This will however usually not cause the entire server to go down. You can determine that by checking if the HTTP response code is 200.
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("HEAD");
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
// Not OK.
}
// < 100 is undetermined.
// 1nn is informal (shouldn't happen on a GET/HEAD)
// 2nn is success
// 3nn is redirect
// 4nn is client error
// 5nn is server error
For more detail about response status codes see RFC 2616 section 10. Calling connect() is by the way not needed if you're determining the response data. It will implicitly connect.
For future reference, here's a complete example in flavor of an utility method, also taking account with timeouts:
/**
* Pings a HTTP URL. This effectively sends a HEAD request and returns <code>true</code> if the response code is in
* the 200-399 range.
* #param url The HTTP URL to be pinged.
* #param timeout The timeout in millis for both the connection timeout and the response read timeout. Note that
* the total timeout is effectively two times the given timeout.
* #return <code>true</code> if the given HTTP URL has returned response code 200-399 on a HEAD request within the
* given timeout, otherwise <code>false</code>.
*/
public static boolean pingURL(String url, int timeout) {
url = url.replaceFirst("^https", "http"); // Otherwise an exception may be thrown on invalid SSL certificates.
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setConnectTimeout(timeout);
connection.setReadTimeout(timeout);
connection.setRequestMethod("HEAD");
int responseCode = connection.getResponseCode();
return (200 <= responseCode && responseCode <= 399);
} catch (IOException exception) {
return false;
}
}
Instead of using URLConnection use HttpURLConnection by calling openConnection() on your URL object.
Then use getResponseCode() will give you the HTTP response once you've read from the connection.
here is code:
HttpURLConnection connection = null;
try {
URL u = new URL("http://www.google.com/");
connection = (HttpURLConnection) u.openConnection();
connection.setRequestMethod("HEAD");
int code = connection.getResponseCode();
System.out.println("" + code);
// You can determine on HTTP return code received. 200 is success.
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
Also check similar question How to check if a URL exists or returns 404 with Java?
Hope this helps.
You could also use HttpURLConnection, which allows you to set the request method (to HEAD for example). Here's an example that shows how to send a request, read the response, and disconnect.
The following code performs a HEAD request to check whether the website is available or not.
public static boolean isReachable(String targetUrl) throws IOException
{
HttpURLConnection httpUrlConnection = (HttpURLConnection) new URL(
targetUrl).openConnection();
httpUrlConnection.setRequestMethod("HEAD");
try
{
int responseCode = httpUrlConnection.getResponseCode();
return responseCode == HttpURLConnection.HTTP_OK;
} catch (UnknownHostException noInternetConnection)
{
return false;
}
}
public boolean isOnline() {
Runtime runtime = Runtime.getRuntime();
try {
Process ipProcess = runtime.exec("/system/bin/ping -c 1 8.8.8.8");
int exitValue = ipProcess.waitFor();
return (exitValue == 0);
} catch (IOException | InterruptedException e) { e.printStackTrace(); }
return false;
}
Possible Questions
Is this really fast enough?Yes, very fast!
Couldn’t I just ping my own page, which I want
to request anyways? Sure! You could even check both, if you want to
differentiate between “internet connection available” and your own
servers beeing reachable What if the DNS is down? Google DNS (e.g.
8.8.8.8) is the largest public DNS service in the world. As of 2013 it serves 130 billion requests a day. Let ‘s just say, your app not
responding would probably not be the talk of the day.
read the link. its seems very good
EDIT:
in my exp of using it, it's not as fast as this method:
public boolean isOnline() {
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnectedOrConnecting();
}
they are a bit different but in the functionality for just checking the connection to internet the first method may become slow due to the connection variables.
Consider using the Restlet framework, which has great semantics for this sort of thing. It's powerful and flexible.
The code could be as simple as:
Client client = new Client(Protocol.HTTP);
Response response = client.get(url);
if (response.getStatus().isError()) {
// uh oh!
}

Categories