My app connects to a server at various times. If I switch between 3g and wifi or vice versa I get a socket timeout error. How do I circumvent this? I read some posts on stackoverflow where people had similar issues and they fixed it by using a new socket. How do make my http request with a new socket?
CODE:
class HttpRunnable implements Runnable
{
private HttpRequestNotification request = null;
public HttpRunnable(HttpRequestNotification req)
{
request = req;
}
public void run()
{
HttpURLConnection conn = null;
try {
System.out.println(request.url);
conn = (HttpURLConnection)(new URL(request.url)).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setRequestMethod(request.verb);
conn.setReadTimeout(20000);
conn.setConnectTimeout(20000);
// If the request contains an explicit auth token, replace our cached copy
if (request.authToken != null && request.authToken.length() > 0)
authorizationToken = request.authToken;
// Add the authorization token, if we have it
if (authorizationToken != null && authorizationToken.length() > 0)
{
conn.setRequestProperty("Authorization", authorizationToken);
}
if (request.postData != null && request.postData.length > 0)
{ // Let the connection know we'll be posting data
conn.addRequestProperty("Content-Type", "application/xml");
conn.setDoOutput(true);
}
conn.connect();
if (request.postData != null && request.postData.length > 0)
{ // Write POST data, if necessary
OutputStream os = conn.getOutputStream();
os.write(request.postData);
os.close();
}
// Grab input stream. If it hasn't occurred already, this will send the Http request over the wire.
InputStream inputStream = conn.getInputStream();
// Save off statusCode, statusMessage, headers
request.statusCode = conn.getResponseCode();
request.statusMessage = conn.getResponseMessage();
Map hdrs = conn.getHeaderFields();
Iterator iter = hdrs.keySet().iterator();
while (iter.hasNext())
{
String key = (String) iter.next();
String val = conn.getHeaderField(key);
if (key != null && val != null)
{
if (key.equalsIgnoreCase("location"))
request.url = val;
request.headers.put(key, val);
}
}
// Extract the message body. We used to extract the content-length header & only
// read that amount of bytes, but we found that sometimes the header is missing.
// Specifically, we found that requests lacked a content-length header in these cases:
// 1) for HTTP 1.1 requests that provide a "transfer-encoding: chunked" header instead
// 2) When running on certain carriers, we found the content-length header to be unreliable
// (DroidX running over Verizon -- full response body was there, but the content-length
// field was waaaay too small.
// To handle all of these cases, we simply read until the end of the stream is reached, then
// convert the result to a byte array. See defect 14288 (over WiFi, some android devices
// return Content-Length; over 3G, they may use transfer-encoding).
ByteArrayOutputStream baos = new ByteArrayOutputStream(10*1024); // This size is an initial size, not max size
while (true)
{
int data = inputStream.read();
if (data < 0)
break;
baos.write(data);
}
request.responseData = baos.toByteArray();
// Extract the auth token, if it's present
if (authorizationToken == null && request.url.endsWith("authenticate") &&
request.statusCode >= 200 && request.statusCode < 300)
{
String authToken = conn.getHeaderField("X-Authorization-Token");
if (authToken != null && authToken.length() > 0)
request.authToken = authorizationToken = authToken;
}
if (request.statusCode >= 200 && request.statusCode < 400)
{
request.onSuccess();
}
else
{
// Note -- this check is included here as well as below for future-proofing. Note that currently
// the J2SE implementation of HttpConnection will throw an IOException if the resopnse code
// is not in the 200-299 range.
if (request.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED &&
!UserAuthService.HttpRequestNotification_Login.class.isInstance(request))
{ // Got a 401 Unauthorized. Since this is not a login request, we want to transition
// the user back to the login screen.
LoginOrchestrator.getInstance().logout();
}
else
request.onError(request.statusCode, "");
}
}
catch (IOException e)
{
try
{
if (conn != null)
{
request.statusCode = conn.getResponseCode();
request.statusMessage = conn.getResponseMessage();
}
}
catch (IOException ex) {}
// Have to check for this HTTP_UNAUTHRORIZED case here as well -- the J2SE HttpConnection
// throws an IOException when the status code is not in the 200-299 range.
if (request.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED &&
!UserAuthService.HttpRequestNotification_Login.class.isInstance(request))
{ // Got a 401 Unauthorized. Since this is not a login request, we want to transition
// the user back to the login screen.
LoginOrchestrator.getInstance().logout();
}
else
request.onError(request.statusCode, e.toString() + ": " + e.getMessage());
}
System.out.flush();
}
}
Just retry connection with new socket and send everything again (in your case send a new HTTP request).
Basically when your Android device switches from WiFi to 3G then its IP address that Web Server sees changes (from your home ISP assigned IP address to the Mobile Carrier gateway IP address). So the old socket becomes useless…
I haven’t used java.net.HttpURLConnection, but I suppose on the timeout exception you will need to redo the same code that was after you called serverAddress.openConnection(). Because this function seems to be the one that creates underlying TCP connection.