I running into an issue routing network calls using OkHttp. I'm trying to send HTTP requests to a local device (via the device's ip) using an Access Point that has no internet access. Due to some changes in Android 5.0, OkHttp will try to route the request over data instead, which then fails.
In Android 5.0's Network API, I could use the NetworkCapabilities class along with the NetworkRequest.Builder in order to tell the request to only go over Wi-Fi, However, I'm already using OkHttp and I don't want to overhaul my code. Is there anyway to do a similar thing with OkHttp?
I came up with a workaround:
final Network currentNetwork = (Network) network;
newClientBuilder.socketFactory(((Network)network).getSocketFactory())
.dns(new Dns() {
#Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
return Arrays.asList(currentNetwork.getAllByName(hostname));
}
});
We’d like to eventually include such functionality directly in OkHttp, and you might be the first volunteer/victim to test things out. What you probably want to do is use OkHttp 2.6’s new Dns interface to call through to the network of your choice. If that works, either the IP address will cause OkHttp to do the right thing anyway, or you’ll also need to replace the SocketFactory.
Related
I'm having problems with my app when making REST requests using retrofit + okhttp on ipv6 networks. The response time appears to be very high when connected to ipv6 networks (approximately 10 seconds for each request). Can you tell me if there is any way to limit the app so that it prioritizes ipv4 networks? I know that this can impact the usability of the app, but the app is for use in more restricted environments, it is not for the general public.
You can set a custom Dns implementation that just filters the Dns.SYSTEM results to IPv4.
https://github.com/yschimke/okurl/blob/master/src/main/kotlin/com/baulsupp/okurl/network/DnsSelector.kt
class DnsSelector() : Dns {
override fun lookup(hostname: String): List<InetAddress> {
return Dns.SYSTEM.lookup(hostname).filter { Inet4Address::class.java.isInstance(it) }
}
}
then set it
val client = OkHttpClient.Builder().dns(DnsSelector()).build()
OkHttp 5 (alpha) supports HappyEyeballs. It's by default in alpha11. This should correctly handle a mix of IPv4 and IPv6 without code changes.
See https://twitter.com/jessewilson/status/1495780761819037697?lang=en-GB for the announcement.
How to check programmatically whether the Wifi network the phone is connected to has internet access ?
I cannot use "ping google.com" type solutions because it does not work on some devices such as Honor 10
Well, in order to decide whether a device is connected to the internet we have to define what "connected to the internet" actually means. As far as I know, the Android SDK doesn't offer any way to check that and I think that is because you have to ping a specific address after all, in order to see if it is reachable.
On my Android device, the WiFi indicator in the status bar shows an exclamation point whenever I am connected to the WiFi network but my internet connection is down. I am not sure, but I think it pings a google server (like 8.8.8.8) behind the scenes in order to find out.
I think the best approach is not to ping Google, rather ping the specific address that you use in your app, for example if you use Last.fm API, ping that instead, because you could get in a situation where the Google server is reachable but the Last.fm API is down. This is just a general example, but the solution depends on your goal.
Just try connecting to whatever it is that you need to talk to, and handle failures in a graceful way.
Pinging something (even the server you want to talk to) isn't reliable, as the server, or some part of the network may block PING.
Pinging something "well known" (like Google's name-server on 8.8.8.8) isn't reliable because it only tells you that it is up, not necessarily that you can reach the server you want to talk to. (Or, it might even be that the "well known" entity is down or unreachable, but your server is working OK).
Doing something other than just trying to connect to what you want risks introducing TOCTOU (Time-of-check to time-of-use) errors.
I've found a solution that is quick and does not require using ping command or having to load a page.
The solution uses Volley, Android's HTTP library:
public static void isInternetAccessWorking(Context context, final InternetAccessListener listener) {
StringRequest stringRequest = new StringRequest(Request.Method.GET, "https://www.google.com",
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
listener.hasInternet(true);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
listener.hasInternet(false);
}
});
Volley.newRequestQueue(context).add(stringRequest);
}
This method is not blocking: the activity or fragment calling isInternetAccessWorking() has to provide a parameter implementing InternetAccessListener that will receive the response
public interface InternetAccessListener {
void hasInternet(Boolean result);
}
Is a way to force OkHttp to do requests over the cellular network?
My app has certain requests that must be sent over the cellular network (3G, LTE, 4G, not Wifi). I'm using Retrofit and OkHttp for all the other network requests, so I would like to use it for these requests also. It looks like ConnectivityManger.registerDefaultNetworkCallback() could help but that only supports API 21+. I would like a solution for API 19+.
Assuming API 21+,
Network network; // filled in from ConnectivityManager.NetworkCallback
OkHttpClient client = new OkHttpClient.Builder()
.socketFactory(network.getSocketFactory())
.build();
you'll have to rebuild the OkHttpClient when the network goes away and comes back, but it's doable. You may be able to use a non-default network explicitly this way (some devices can keep the data connection up even when connected to and defaulting to WiFi).
On API 19 you may check ConnectivityManager.getActiveNetworkInfo() and refuse to operate if the type is WiFi, and check again from a BroadcastReceiver registered for android.net.conn.CONNECTIVITY_CHANGE.
Since you need a solution for lower API than 21, you can try with ConnectivityManager.
You have this method setNetworkPreference(type) where type is either ConnectivityManager.TYPE_WIFI or ConnectivityManager.TYPE_MOBILE. In your case is TYPE_MOBILE.
This method was deprecated in API 21+, you should check for current Android version.
Note that this call requires android.Manifest.permission.CHANGE_NETWORK_STATE permission.
I want to simulate the no network case when using RetroFit and MockWebServer.
Im currently testing using Espresso and supplying the MockWebServers url to the RestAdapter before I start my tests. This works great for mocking server responses and so on but I cant see a simple way to script the java.net.ConnectException exception thrown when a device has no network. I can see the MockResponse allows throttling simulation and so on but not a custom exception.
I know I could go the root of mocking the actual web api interface used by retrofit but I would like to use the same approach as my other tests if possible by using MockWebServer.
I imagine I've just missed something simple :)
Thanks
Retrofit has a retrofit-mock module which offers a MockRestAdapter class whose purpose is to simulate network delay and errors.
This is a used in conjunction with the normal RestAdapter to create an instance of your service. You can see a full example in the samples/mock-github-client/ folder of the repo: https://github.com/square/retrofit/tree/parent-1.9.0/retrofit-samples/mock-github-client
MockRestAdapter offers these APIs:
setDelay - Set the network round trip delay, in milliseconds.
setVariancePercentage - Set the plus-or-minus variance percentage of the network round trip delay.
setErrorPercentage - Set the percentage of calls to calculateIsFailure() that return true.
In your test, you can call setErrorPercentage(100) to guarantee that a network error will occur. By default the amount of time for the error to be thrown is anywhere from 0 to 3x the delay. Set the delay to 0 for instant results.
The easiest way to simulate network issues with MockWebServer is by setting the SocketPolicy to SocketPolicy.DISCONNECT_AT_START, SocketPolicy.NO_RESPONSE or etc:
MockWebServer server = new MockWebServer();
MockResponse response = new MockResponse()
.setSocketPolicy(SocketPolicy.DISCONNECT_AT_START);
server.enqueue(response);
This way you can simulate network errors like connection prematurely closed before response or Timeout exceptions
to be clear with whe types of exceptions here you can see the differences:
Internet Connection Error
So you can get two types:
UnknownHostException - When you don't have internet or unknown host... to simulate this, set to the adapter an incorrect end point.
ConnectException - mockwebserver can throw a timeout exception. You can see how to do it here:
https://github.com/square/okhttp/tree/master/mockwebserver
Really I don't know how your code is, but I hope this is useful
I don't know if it's useful, but you can simulate a timeout with MockWebServer:
String contentType = "Content-type: application/json";
MockResponse response = MockResponse().setResponseCode(statusCode).setBody(responseBody).addHeader(contentType);
MockWebServer proxy = new MockWebServer();
proxy.enqueue(response);
proxy.setBodyDelayTimeMs(timeoutDelay));
proxy.play(10000); //Number port - Important!!!
Note: Please, set HttpClient of your code (e.g. AsyncHttpClient) with that number port for testing purpose.
What worked for me is pointing the Retrofit URL (end-point) to a random non-existent host .
I used "thisIsARandomHost.com" and bingo i was able to mock the unknown host exception.
For example : setAPP_URL = "http://thisIsARandomHost.com/";
Here , setAPP_URL is the string variable that contains my url which I pass on to retrofit , when I want to mock unknown host exception in my tests I set the url as above .For all other purposes it is set to the valid URL required by my app.
As stated in above answers , MockWebServer is a great library for mocking retrofit responses , but you don't need that library for mocking this exception . For mocking all other exceptions I would recommend MockWebServer , I use it a lot in my project for testing responses.
Note : You can set the app url to anything as long as that url doesn't exist on the web.
mockWebServer.shutdown() before executing your unit test is fine.
Internally it closes the socket connection:
serverSocket!!.close()
I perform some large downloads. I start a download being connected to 3G, all is fine. Then, I switch to WiFi connection, but the request returns a timeout exception. I have used HttpClient library. I have implemented a retry mechanism, so, when the request returns an exception, it sleeps for 0.5 seconds and tries to execute again and again. I would expect that, after connecting to a WiFi, the Http request could execute. But it seems that the Http execute method returns a null response, all the time after that. Very strange, if I commute again to 3G, the execute method returns again a good response. Can anyone help me please :) ?
First, it seems that it may be more convenient to use DownloadManager for large files - it handles retry and everything.
As for HttpClient - it's known to have some issues, but i'm not sure if you bumped into one of them or just overlooked something. It's been deprecated as of Gingerbread, you may want to try HttpUrlConnection instead, it's said to have less problems than HttpClient.
Also, when switching between WIFI and cell connections, HttpClient may need to be reinitialized completely, there's http range header to tell server which byte you want to continue downloading from. But again, I suggest you give DownloadManager a try, it may save you a lot of time.
This might be a routing problem:
when switching between different network types usually the local ip address and, more important, the local routing table changes, due to a different gateway being used. This means that packages that traveled fine between client and server wont reach any destination after a network change, if they are send the same route. Most likely your client implementation has to be notified of the change or even restartet completely, so that the routing strategy is reinitilized.
If the documentation of the implementation components you use dont reveal anything you could try to track this down using a package sniffer like wireshark. Typically packages running into nirwhana show up easily there.