How do I persist cookies when using HTTPUrlConnection?

I've begun using the recommended HTTPUrlConnection and moved away from the DefaultHTTPClient. One of the things that I haven't been able to glue back together is the use of a persistent cookie store. I'd like to simply attach a custom cookie handler/manager to my connection to store the cookies. The Android documentation hasn't been very helpful as it wraps up the subject about cookies in two lines.

I've been using LoopJ's PersistentCookieStore earlier and that worked beautifully.

Any idea on how I could set up a persistent cookie store in Android that I can attach to my HTTPUrlConnection that saves and retrieves cookies automatically?

Thanks


Solution 1:

Its' taken me a few hours but I managed to build a custom cookie storage myself.

You have to attach this by doing this:

public class application extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
       CookieManager cmrCookieMan = new CookieManager(new MyCookieStore(this.objContext), CookiePolicy.ACCEPT_ALL);
       CookieHandler.setDefault(cmrCookieMan);
       }
    }

Here's the actual storage:

/*
 * This is a custom cookie storage for the application. This
 * will store all the cookies to the shared preferences so that it persists
 * across application restarts.
 */
class MyCookieStore implements CookieStore {

    /*
     * The memory storage of the cookies
     */
    private Map<URI, List<HttpCookie>> mapCookies = new HashMap<URI, List<HttpCookie>>();
    /*
     * The instance of the shared preferences
     */
    private final SharedPreferences spePreferences;

    /*
     * @see java.net.CookieStore#add(java.net.URI, java.net.HttpCookie)
     */
    public void add(URI uri, HttpCookie cookie) {

        System.out.println("add");
        System.out.println(cookie.toString());

        List<HttpCookie> cookies = mapCookies.get(uri);
        if (cookies == null) {
            cookies = new ArrayList<HttpCookie>();
            mapCookies.put(uri, cookies);
        }
        cookies.add(cookie);

        Editor ediWriter = spePreferences.edit();
        HashSet<String> setCookies = new HashSet<String>();
        setCookies.add(cookie.toString());
        ediWriter.putStringSet(uri.toString(), spePreferences.getStringSet(uri.toString(), setCookies));
        ediWriter.commit();

    }

   /*
    * Constructor
    * 
    * @param  ctxContext the context of the Activity
    */
    @SuppressWarnings("unchecked")
    public MyCookieStore(Context ctxContext) {

        spePreferences = ctxContext.getSharedPreferences("CookiePrefsFile", 0);
        Map<String, ?> prefsMap = spePreferences.getAll();

        for(Map.Entry<String, ?> entry : prefsMap.entrySet()) {

            for (String strCookie : (HashSet<String>) entry.getValue()) {

                if (!mapCookies.containsKey(entry.getKey())) {

                    List<HttpCookie> lstCookies = new ArrayList<HttpCookie>();
                    lstCookies.addAll(HttpCookie.parse(strCookie));

                    try {

                        mapCookies.put(new URI(entry.getKey()), lstCookies);

                    } catch (URISyntaxException e) {

                        e.printStackTrace();

                    }

                } else {

                    List<HttpCookie> lstCookies = mapCookies.get(entry.getKey());
                    lstCookies.addAll(HttpCookie.parse(strCookie));

                    try {

                        mapCookies.put(new URI(entry.getKey()), lstCookies);

                    } catch (URISyntaxException e) {

                        e.printStackTrace();

                    }

                }

                System.out.println(entry.getKey() + ": " + strCookie);

            }

        }

    }

    /*
     * @see java.net.CookieStore#get(java.net.URI)
     */
    public List<HttpCookie> get(URI uri) {

        List<HttpCookie> lstCookies = mapCookies.get(uri);

        if (lstCookies == null)
            mapCookies.put(uri, new ArrayList<HttpCookie>());

        return mapCookies.get(uri);

    }

    /*
     * @see java.net.CookieStore#removeAll()
     */
    public boolean removeAll() {

        mapCookies.clear();
        return true;

    }        

    /*
     * @see java.net.CookieStore#getCookies()
     */
    public List<HttpCookie> getCookies() {

        Collection<List<HttpCookie>> values = mapCookies.values();

        List<HttpCookie> result = new ArrayList<HttpCookie>();
        for (List<HttpCookie> value : values) {                
            result.addAll(value);                
        }

        return result;

    }

    /*
     * @see java.net.CookieStore#getURIs()
     */
    public List<URI> getURIs() {

        Set<URI> keys = mapCookies.keySet();
        return new ArrayList<URI>(keys);

    }

    /*
     * @see java.net.CookieStore#remove(java.net.URI, java.net.HttpCookie)
     */
    public boolean remove(URI uri, HttpCookie cookie) {

        List<HttpCookie> lstCookies = mapCookies.get(uri);

        if (lstCookies == null)
            return false;

        return lstCookies.remove(cookie);

    }

}

Solution 2:

I used the answer above but changed my add method to the following to handle more than one cookie from the same URI (this cookie store with GAE was treating the session token and the remember token as two separate cookies from the same URI for some reason):

public void add(URI uri, HttpCookie cookie) {


    List<HttpCookie> cookies = mapCookies.get(uri);
    if (cookies == null) {
        cookies = new ArrayList<HttpCookie>();
        mapCookies.put(uri, cookies);
    }
    cookies.add(cookie);

    Editor ediWriter = spePreferences.edit();
    HashSet<String> setCookies = new HashSet<String>();
    setCookies.add(cookie.toString());
    HashSet<String> emptyCookieSet = new HashSet<String>();
    if(spePreferences.contains(uri.toString())){
        emptyCookieSet = (HashSet<String>) spePreferences.getStringSet(uri.toString(), emptyCookieSet);
        if(!emptyCookieSet.isEmpty()){
            if(!emptyCookieSet.contains(cookie.toString())){
            emptyCookieSet.add(cookie.toString());
            ediWriter.putStringSet(uri.toString(), emptyCookieSet);
            }
        }
    }
    else{
        ediWriter.putStringSet(uri.toString(), setCookies);
    }
    ediWriter.commit();
} 

And to access and create a combined cookie:

MyCookieStore store = new MyCookieStore(this.context, false);
String cookie = TextUtils.join(",", store.get(new URI(URLString)));

Attach to connection:

URL urlToRequest = new URL(stringPath);
HttpURLConnection urlConnection = (HttpURLConnection) urlToRequest.openConnection();
urlConnection.setRequestProperty("Cookie", cookie); 

Solution 3:

Checkout the implementation in the link below. It saves the cookies by hostname like the original java.net.InMemoryCookieStore implementation does.

Besides that it contains a SerializableHttpCookie to be able to serialize the complete HashMap into the SharedPreferences.

https://gist.github.com/jacobtabak/78e226673d5a6a4c4367

Solution 4:

There are some basic problems in many custom CookieStore implementation.

The first problem is HttpCookie serialization into string - the HttpCookie.toString() method is not acceptable for this because its result is not suitable for the HttpCookie.parse(String header) method.

The second problem: most of CookieStore implementation (for example, here) does not takes into account the format of HttpCookie.maxAge field. This is a number of seconds to cookie live. But if you simply persist its value and after some time unpersist it, it will be wrong. You must convert maxAge field into something like "expire_at" and persist it instead of maxAge.