1

My Android code sends data all the time and it seems to be working until a totally unexpected IOException is thrown, freezing everything. I don't understand why I can send several post requests without problem until the Exception appears, almost randomly. Here's my code:

public class HttpsClient {

private final static String TAG = "HttpsClient";

private final static String TOKEN_HEADER_KEY = "Token";

private final String urlString;

private SSLContext sslContext;

// application-specific HTTP header
private String TokenHeaderValue = null;



public HttpsClient(String host, String path) {
    // this.sslContext will be init'ed in open()
    this.urlString = "https://" + host + ":443/" + path;
}


public boolean open() {
    try {
        this.sslContext = SSLContext.getInstance("TLS");
        this.sslContext.init(null, null, new java.security.SecureRandom());
        return true;
    } catch (NoSuchAlgorithmException e) {
        Logger.e(TAG, "NoSuchAlgorithmException:");
    } catch (KeyManagementException e) {
        Logger.e(TAG, "KeyManagementException:");
    }

    return false;
}


public byte[] send(byte[] req) {

    Logger.d(TAG, "sending " + Utils.byteArrayToString(req) + " to " + this.urlString);

    URL url;
    try {
        url = new URL(this.urlString);
    } catch (MalformedURLException e) {
        Logger.e(TAG, "MalformedURLException:");
        return null;
    }
    HttpsURLConnection connection;
    try {
        connection = (HttpsURLConnection) url.openConnection();
    } catch (IOException e) {
        Logger.e(TAG, "send IOException 1 " + ((null == e.getMessage()) ? e.getMessage() : ""));
        e.printStackTrace();
        return null;
    }

    connection.setDoInput(true);
    connection.setDoOutput(true);
    connection.setRequestProperty("Connection", "close");
    try {
        connection.setRequestMethod("POST");

    } catch (ProtocolException ignored) { }
    connection.setSSLSocketFactory(this.sslContext.getSocketFactory());
    connection.setReadTimeout(3000);



    if ( this.TokenHeaderValue != null )
        connection.setRequestProperty(TOKEN_HEADER_KEY, this.TokenHeaderValue);



    {
        final Map<String, List<String>> requestProps = connection.getRequestProperties();
        Logger.d(TAG, requestProps.size() + " Request header(s):");
        for (Map.Entry<String, List<String>> entry : requestProps.entrySet())
            for (String value : entry.getValue())
                Logger.d(TAG, " " + entry.getKey() + ": <" + value + ">");
    }

    try {
        // open up the output stream of the connection 
        DataOutputStream output = new DataOutputStream(connection.getOutputStream()); 

        // write out the data 
        output.write(req, 0, req.length);
        output.flush();


        Logger.i(TAG, "Response Code: " + connection.getResponseCode());
        Logger.i(TAG, "Response Message: " + connection.getResponseMessage()); 
    } catch (SocketTimeoutException e) {
        Logger.e(TAG, "SocketTimeoutException:" + ((null == e.getMessage()) ? e.getMessage() : ""));
        return null;
    } catch (IOException e) { // FAILS HERE !!!!!!!
        Logger.e(TAG, "send IOException 2 " + ((null == e.getMessage()) ? e.getMessage() : ""));
        return null;
    }


    final Map<String, List<String>> responseHeaderFields = connection.getHeaderFields();
    Logger.d(TAG, responseHeaderFields.size() + " Response header(s):");
    for (Map.Entry<String, List<String>> entry : responseHeaderFields.entrySet()) {
        final String key = entry.getKey();
        if ( (null != key) && key.equals(TOKEN_HEADER_KEY) )
            this.TokenHeaderValue = entry.getValue().get(0);
        for (String value : entry.getValue())
            Logger.d(TAG, " " + key + ": <" + value + ">");
    }


    // read response
    ArrayList<Byte> response = new ArrayList<Byte>();

    try {
        DataInputStream input = new DataInputStream(connection.getInputStream()); 

        // read in each character until end-of-stream is detected
        for( int c = input.read(); c != -1; c = input.read() ) {
            response.add((byte) c);
        }
        Logger.w(TAG, "Https connection is " + connection);
        connection.disconnect();
        Logger.w(TAG, "Https connection is " + connection);
        input.close();

    } catch (IOException e) {
        Logger.e(TAG, "send IOException 3 " + ((null == e.getMessage()) ? e.getMessage() : ""));
        return null;
    }

    if ( 0 == response.size() ) {
        Logger.w(TAG, "response is null");

        return null;
    }

    // else

    byte[] result = new byte[response.size()];
    for (int i = 0; i < result.length; i++)
        result[i] = response.get(i).byteValue();

    Logger.i(TAG, "Response payload: " + Utils.byteArrayToString(result));



    return result;          
}
}

I repeat: code works 'til after many post send, then crashes 'cause of an IO Exception. Moreover, server is fine, there's something wrong in my code.

Here's the entire error stack trace:

send IOException 2 

0: org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read_byte(Native Method)

1: org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:783)

2: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.readLine(HttpURLConnectionImpl.java:671)

3: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.readResponseHeaders(HttpURLConnectionImpl.java:699)

4: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getFromNetwork(HttpURLConnectionImpl.java:1088)

5: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1041)

6: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:736)

7: org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:146)

8: com.bitstorms.gui.channels.HttpsClient.send(HttpsClient.java:117)

9: com.bitstorms.gui.apps.Application$MyRunnable.run(Application.java:81)

10: java.lang.Thread.run(Thread.java:1020)
jere
  • 4,306
  • 2
  • 27
  • 38

1 Answers1

1

The suspicious point is that you never properly release streams. For instance, you create DataOutputStream output, use it, but never close. Try to change the code that uses finally to close the stream:

DataOutputStream output = null;
try {
    output = new DataOutputStream(connection.getOutputStream());
    ...
} catch (...) {
    ...
} finally {
    if (output != null) {
         try {
             output.close();
         } catch (IOException ignored) {}
    }
}

The same should be done with DataInputStream input and the connection itself (it should be guaranteedly closed in some global finally section).

Vit Khudenko
  • 28,288
  • 10
  • 63
  • 91
  • I tried to put the finally section you're talking about to close streams (input also) but nothing, code's behaviour is the same... I don't understand... – Francesco Novecento Nicolosi Nov 23 '12 at 13:48
  • @FrancescoNovecentoNicolosi: Then I am out of ideas. Could you post the error stacktrace (you can edit your initial post and append it with the stacktrace). Probably it could give some ideas. – Vit Khudenko Nov 23 '12 at 16:38
  • Just edited my question. Provided the entire stack trace, did you see it? – Francesco Novecento Nicolosi Nov 24 '12 at 08:49
  • I'm using a Samsung Galaxy tablet to try this client, with Android 3.2, are there some known bug in that release about httpsurlconnection? – Francesco Novecento Nicolosi Nov 24 '12 at 09:00
  • @FrancescoNovecentoNicolosi: Just checked the stack trace. As you see it happens at the native method `NativeCrypto.SSL_read_byte()`. I have no idea why it fails. Its API doc just says it may throw the `IOException`, but without any explanation of possible reason(s). – Vit Khudenko Nov 25 '12 at 11:44
  • @FrancescoNovecentoNicolosi: I am not aware of any known bugs for this. – Vit Khudenko Nov 25 '12 at 11:46
  • @FrancescoNovecentoNicolosi: I can propose to think about 2 ideas on how to workaround this. 1st can be related to `SSLContext`. Do you really need to create/init it explicitly? Shouldn't it be handled automatically even if you don't create/init it explicitly? Even if you really need to create/init it explicitly, then try not to reuse the instance (make sure you always call `open()` before `send(byte[] req)`. – Vit Khudenko Nov 25 '12 at 11:52
  • @FrancescoNovecentoNicolosi: 2nd idea is to switch from `HttpsURLConnection` to `HttpClient`. BTW, despite Google says "use HttpURLConnection, because we fixed it (in 2.2 it had some issues) and optimized", I still prefer `HttpClient` because it is more OOP'ish. :) What I dislike is `URL.openConnection()` signature, because URL is just a URL (a little piece of info, a link) it should not open a connection! – Vit Khudenko Nov 25 '12 at 12:00
  • @Ahrimed I did what you say, HttpClient class is surely more OOP'ish, you're right, but the application throws the same exception with one more information on the error stack trace that says : 11-25 17:15:07.570: E/HttpsClient(3525): Read error: ssl=0x466590: I/O error during system call, Connection reset by peer. Other messages on the stack trace are the same. What could it be? – Francesco Novecento Nicolosi Nov 25 '12 at 16:17
  • @FrancescoNovecentoNicolosi: Sorry, I have no idea.. Looks like similar to http://stackoverflow.com/questions/11478814/android-query-random-sslexceptions and http://stackoverflow.com/questions/6319977/android-random-exceptions-on-http-calls-why. – Vit Khudenko Nov 25 '12 at 17:04
  • I solved my problem: it was an error thrown by server that doesn't have relationship with my http connection, which was fine. However I have written a different code to use http connection, following ahrimed suggestion: I used HttpClient 'cause it's more OOP'ish and more clean. – Francesco Novecento Nicolosi Nov 30 '12 at 15:21
  • @FrancescoNovecentoNicolosi: I think you should not accept my answer as a solution, because others may think it is a solution while actually it is not! However you can just upvote my answer since it provided you some help. – Vit Khudenko Nov 30 '12 at 15:44
  • According to Stack Overflow, I have not the reptutation to upvote your answer yet, sorry. But it was very useful, and I read your answers everywhere on Stack Overflow, you're great ^^ – Francesco Novecento Nicolosi Dec 02 '12 at 08:51
  • @FrancescoNovecentoNicolosi: Thanks! :) However once you get the required reputation, please do that. – Vit Khudenko Dec 02 '12 at 21:22