Search code examples
androidhttpurlconnectionpersistent-connection

Android HttpUrlConnection w Persistent Connection


Scenario

Users have to login to the server with this app. Later in the app we need to check their login status against the server. We used to do this by keeping track of the last used HttpClient. Recently we switched to HttpUrlConnection but the so-called persistent connections are not working.

Question

I wrote this test app to see if the connections were persistent. I get xml back from both URLs but the connection is not behaving like it's consistent. How can I get this to work?

Note: Everything works as expected if you go to the Login url in a browser and then go to the GetUserInfo url in the same browser.

MainActivity.java

package com.mediajackagency.test;

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;

public class MainActivity extends AppCompatActivity {

    public Button signInBtn = null;
    public Button getUserInfoBtn = null;
    public TextView xmlTextView = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        this.xmlTextView = (TextView)findViewById(R.id.xmlTxtView);

        this.signInBtn = (Button)findViewById(R.id.signInBtn);
        this.signInBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AsyncTask task = new AsyncTask() {
                    @Override
                    protected Object doInBackground(Object[] params) {
                        String xml = loadUrl("https://www.fake.site/Login?userName=test&password=pass123");

                        return xml;
                    }

                    @Override
                    protected void onPostExecute(Object o) {
                        super.onPostExecute(o);
                        xmlTextView.setText(o.toString());
                    }
                };
                task.execute();
            }
        });

        this.getUserInfoBtn = (Button)findViewById(R.id.getUserInfoBtn);
        this.getUserInfoBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AsyncTask task = new AsyncTask() {
                    @Override
                    protected Object doInBackground(Object[] params) {
                        String xml = loadUrl("https://www.fake.site/GetCurrentUser");

                        return xml;
                    }

                    @Override
                    protected void onPostExecute(Object o) {
                        super.onPostExecute(o);
                        xmlTextView.setText(o.toString());
                    }
                };
                task.execute();
            }
        });
    }

    public String loadUrl(String url) {
        URI uri = MainActivity.encodeUrl(url);
        Log.i("XMLParser", "Get URL: " + url);

        String xml = null;
        URL link;
        BufferedReader reader = null;
        StringBuilder stringBuilder = null;
        InputStream is = null;
        HttpURLConnection connection = null;

        try {
            link = new URL(url);
            connection = (HttpURLConnection) link.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            is = connection.getInputStream();
            reader = new BufferedReader(new InputStreamReader(is));
            stringBuilder = new StringBuilder();

            String line = null;
            while ((line = reader.readLine()) != null)
            {
                stringBuilder.append(line + "\r");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    if(reader != null) reader.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    if(is != null) is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    if(connection != null) connection.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        try {
            xml = stringBuilder.toString();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return xml;
    }

    public static URI encodeUrl(String url) {
        URL urlObject;
        URI uri = null;
        try {
            urlObject = new URL(url);
            uri = new URI(urlObject.getProtocol(), urlObject.getUserInfo(), urlObject.getHost(),
                    urlObject.getPort(), urlObject.getPath(), urlObject.getQuery(), urlObject.getRef());

        } catch (Exception e) {
            e.printStackTrace();
        }
        return uri;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Solution

  • Solved With Cookies

    After days / hours of working on this I found that persistent connections (http.keepalive) with HttpUrlConnection is not all it's cracked up to be:

    1) You need to make sure the InputStream and HttpUrlConnection are closed before you can reuse the connection and even then it may not always be reusable.

    2) Open TCP connections can be resource hogs.

    After finding & testing the idea of using cookies with HttpUrlConnection I decided to go that route as it's fundamentally more sound and performs better than my original idea.