Wednesday, 12 April 2017

Using HttpUrlConnection in Android

Background

Android has two options for doing network operations using Http clients -
  1. Apache HttpClient
    1. DefaultHttpClient
    2. AndroidHttpClient
  2. HttpUrlConnection


Back in 2011 Android developers announced that Android team is not actively working on Apache HTTP Client and that  new android applications should use HttpURLConnection and that is where they will be spending their energy going forward.

For more details you can read their blog -  Android’s HTTP Clients 

It's been a long way since they has posted that post and here we are. Android Http client was totally removed from Android 6.0 (M). Ofcourse you can manually plug it in. But android developer site says the following -

Android 6.0 release removes support for the Apache HTTP client. If your app is using this client and targets Android 2.3 (API level 9) or higher, use the HttpURLConnection class instead. This API is more efficient because it reduces network use through transparent compression and response caching, and minimizes power consumption. To continue using the Apache HTTP APIs, you must first declare the following compile-time dependency in your build.gradle file:

android {
    useLibrary 'org.apache.http.legacy'
}


But I would recommend don't do this unless some legacy API needs this. Move on to HttpUrlConenction. In this post we will see how we can use HttpUrlConnection for network operations in android.

Using HttpUrlConnection in Android

HttpURLConnection is a general-purpose, lightweight HTTP client suitable for most applications. A simple snippet would be- 

        URL url = new URL("http://google.com");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        try {
            InputStream in = new BufferedInputStream(urlConnection.getInputStream());
            readStream(in);
        } finally {
            urlConnection.disconnect();
        }

So you open a connection by calling openConnection() on an URL instance. You can then set the type of request using setRequestMethod(). It can be GET, POST etc. Then you read the stream by calling urlConnection.getInputStream(). Finally you call disconnect() to release your connection.


NOTE : By default HttpUrlConnection will use GET as request type i.e you dont have to mention  urlConnection.setRequestMethod("GET"); . For others as started above you can use - setRequestMethod();

NOTE :  Connection is actually established when you call getInputStream() or getOutputStream() or getResponseCode() on your httpUrlConenction instance.

NOTE : If your response is not 2** then it would be an error(You can read the response code by calling getResponseCode() method). In that case you need tor read error stream. You can do that by using getErrorStream() to read the error response. The headers can be read in the normal way using getHeaderFields().
 

How disconnect() works?

    Each HttpURLConnection instance is used to make a single request but the underlying network connection to the HTTP server may be transparently shared by other instances. Calling the close() methods on the InputStream or OutputStream of an HttpURLConnection after a request may free network resources associated with this instance but has no effect on any shared persistent connection. Calling the disconnect() method may close the underlying socket if a persistent connection is otherwise idle at that time.   

 -> So basically your HttpUrlConnection instance may get GCed but the underlying TCP socket will get pooled and reused unless you call disconnect() on it in which case it may or may not close the socket. So if you are not reconnecting to same URL it is safe to always call disconnect and let android handle the socket pooling and closing part.

Android docs clearly states -  Disconnecting releases the resources held by a connection so they may be closed or reused.

Handling request body

Sometime a request made to a server may need a request body to be supplied. For that you can use the httpUrlConnection instance's outputStream.

        URL url = new URL("http://test.com");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("POST");
        urlConnection.setDoOutput(true);
        urlConnection.setChunkedStreamingMode(0);
        try {
            OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
            writeStream(out);

            InputStream in = new BufferedInputStream(urlConnection.getInputStream());
            readStream(in);
        } finally {
            urlConnection.disconnect();
        }

Make sure you set setDoOutput(true) if you have request body to be sent. Similarly set setDoInput(true) if you are reading back from the connenction. If you just want to see response code using - getResponseCode() method that these are not required.

NOTE : Notice how we have use BufferedStreams over input/output streams. They are used for performance reasons - so that data is buffered and we can easily read/write it.

Setting headers and connection timeouts

If you are familiar with HttpClient then you must have set connection and socket timeouts. In HttpUrlConenction you need to do following -

        urlConnection.setConnectTimeout(30000);
        urlConnection.setReadTimeout(30000);

These are in milliseconds. I have set it to half a minute above. To set headers in the request you can do -

urlConnection.setRequestProperty("headerName", "headerValue");

So lets say you want to set user-agent in your request you do -

urlConnection.setRequestProperty("User-Agent", "Mozilla");

Similarly you can enable disable redirects using -
urlConnection.setInstanceFollowRedirects(false);

Making secure connection (https)

If you are working with https (secure connection) protocol then your actual class would be - HttpsUrlConnection.

NOTE :  You can still use HttpUrlConnection since HttpUrlConenction extends HttpsURLConnection (by polymorphism).

HttpsUrlConnection will allow you to override HostnameVerifier and SSLSocketFactory.

Eg.

static {
    TrustManager[] trustAllCertificates = new TrustManager[] {
        new X509TrustManager() {
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null; // Not relevant.
            }
            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
                // Do nothing. Just allow them all.
            }
            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
                // Do nothing. Just allow them all.
            }
        }
    };

    HostnameVerifier trustAllHostnames = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true; // Just allow them all.
        }
    };

    try {
        System.setProperty("jsse.enableSNIExtension", "false");
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCertificates, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames);
    }
    catch (GeneralSecurityException e) {
        throw new ExceptionInInitializerError(e);
    }


Sample take from this SO discussion.

NOTE :   Calling HttpsURLConnection.setDefaultSSLSocketFactory() or HttpsURLConnection.setDefaultHostnameVerifier() will set it up for all Https connections. If you want to set it for specific connections then type cast it to HttpsUrlConenction instance and then call set methods on it.

Handling multipart/ form-data

 You'd normally use multipart/form-data encoding for mixed POST content (binary and character data). The encoding is in more detail described in RFC2388.

String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "\r\n"; // Line separator required by multipart/form-data.
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (
    OutputStream output = connection.getOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) {
    // Send normal param.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
    writer.append(CRLF).append(param).append(CRLF).flush();

    // Send text file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"textFile\"; filename=\"" + textFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
    writer.append(CRLF).flush();
    Files.copy(textFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // Send binary file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
    writer.append("Content-Transfer-Encoding: binary").append(CRLF);
    writer.append(CRLF).flush();
    Files.copy(binaryFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // End of multipart/form-data.
    writer.append("--" + boundary + "--").append(CRLF).flush();

Again code is taken from this SO discussion.

Other advantages of using HttpUrlConnection

  • HttpUrlConnection by default honors system proxy. However if you want to specify a custom proxy then you can do so with API - url.openConnection(proxy)
  • It also has transparent response compression. It will automatically add the header - Accept-Encoding: gzip to outgoing responses and also handle it automatically for incoming connections.
  •  It also attempts to connect with Server Name Indication (SNI) which allows multiple HTTPS hosts to share an IP address.

Related Links

No comments:

Post a Comment

t> UA-39527780-1 back to top