Using Cookies across Activities when using HttpClient

44,763

Solution 1

(As promised a solution to this. I still don't like it and feel like I'm missing out on the "Correct" way of doing this but, it works.)

You can use the CookieManager to register your cookies (and therefore make these cookies available between apps) with the following code:

Saving cookies into the CookieManager:

List<Cookie> cookies = httpClient.getCookieStore().getCookies();

if(cookies != null)
{
    for(Cookie cookie : cookies)
    {
        String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();                        
        CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);  
    }
}
CookieSyncManager.getInstance().sync();

Checking for cookies on specified domain: if(CookieManager.getInstance().getCookie(URI_FOR_DOMAIN)

To reconstruct values for HttpClient:

DefaultHttpClient httpClient = new DefaultHttpClient(params);
String[] keyValueSets = CookieManager.getInstance().getCookie(URI_FOR_DOMAIN).split(";");
for(String cookie : keyValueSets)
{
    String[] keyValue = cookie.split("=");
    String key = keyValue[0];
    String value = "";
    if(keyValue.length>1) value = keyValue[1];
    httpClient.getCookieStore().addCookie(new BasicClientCookie(key, value));
}

Solution 2

CookieManager is used by the Java's internal HTTP client. It has nothing to do with Apache HttpClient.

In your code you always create for each request a new instance of HttpClient and therefore a new CookieStore instance, which obviously gets garbage collected along with all cookies stored in as soon as that HttpClient instance goes out of scope.

You should either

(1) Re-use the same instance of HttpClient for all logically related HTTP requests and share it between all logically related threads (which is the recommended way of using Apache HttpClient)

(2) or, at the very least, share the same instance of CookieStore between logically related threads

(3) or, if you insist on using CookieManager to store all your cookies, create a custom CookieStore implementation backed by CookieManager

Solution 3

In my application server wants to use same session with same cookies... After few hours of "googling" and painful headache I just saved cookies to SharedPreference or just put in some object and set DefaultHttpClient with same cookies again ... onDestroy just remove SharedPreferences ... that's all:

  1. First copy SerializableCookie class to your package: SerializableCookie

Look the following example:

public class NodeServerTask extends AsyncTask<Void, Void, String> {
private DefaultHttpClient client;


protected String doInBackground(Void... params) {
  HttpPost httpPost = new HttpPost(url);
    List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
    urlParameters.add(nameValuePair);
    try {
        httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
        List<Cookie> cookies = loadSharedPreferencesCookie();
        if (cookies!=null){
            CookieStore cookieStore = new BasicCookieStore();
            for (int i=0; i<cookies.size(); i++)
                cookieStore.addCookie(cookies.get(i));
            client.setCookieStore(cookieStore);
        }
        HttpResponse response = client.execute(httpPost);
        cookies = client.getCookieStore().getCookies();

        saveSharedPreferencesCookies(cookies);

// two methods to save and load cookies ...

 private void saveSharedPreferencesCookies(List<Cookie> cookies) {
    SerializableCookie[] serializableCookies = new SerializableCookie[cookies.size()];
    for (int i=0;i<cookies.size();i++){
        SerializableCookie serializableCookie = new SerializableCookie(cookies.get(i));
        serializableCookies[i] = serializableCookie;
    }
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    SharedPreferences.Editor editor = preferences.edit();
    ObjectOutputStream objectOutput;
    ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
    try {
        objectOutput = new ObjectOutputStream(arrayOutputStream);


        objectOutput.writeObject(serializableCookies);
        byte[] data = arrayOutputStream.toByteArray();
        objectOutput.close();
        arrayOutputStream.close();

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        Base64OutputStream b64 = new Base64OutputStream(out, Base64.DEFAULT);
        b64.write(data);
        b64.close();
        out.close();

        editor.putString("cookies", new String(out.toByteArray()));
        editor.apply();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private List<Cookie> loadSharedPreferencesCookie() {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
    byte[] bytes = preferences.getString("cookies", "{}").getBytes();
    if (bytes.length == 0 || bytes.length==2)
        return null;
    ByteArrayInputStream byteArray = new ByteArrayInputStream(bytes);
    Base64InputStream base64InputStream = new Base64InputStream(byteArray, Base64.DEFAULT);
    ObjectInputStream in;
    List<Cookie> cookies = new ArrayList<Cookie>();
    SerializableCookie[] serializableCookies;
    try {
        in = new ObjectInputStream(base64InputStream);
        serializableCookies = (SerializableCookie[]) in.readObject();
        for (int i=0;i<serializableCookies.length; i++){
            Cookie cookie = serializableCookies[i].getCookie();
            cookies.add(cookie);
        }
        return cookies;
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}

Google Luck

Solution 4

I had the same problem. Since I am using Volley and not HTTPClient directly the Singleton for HTTPClient didn't seem like the correct solution. But using that same idea I simply saved the CookieManager in my application singleton. So if app is my application singleton then in the onCreate() of each activity I add this:

    if (app.cookieManager == null) {
        app.cookieManager = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
    }
    CookieHandler.setDefault(app.cookieManager);

To make this even better, all of my activities are subclasses of MyAppActivity so all I have to do is put it in the onCreate for MyAppActivity and then all of my activities inherit this functionality. Now all my activities are using the same cookie manager and my web service session is shared by all the activities. Simple and it works great.

Share:
44,763

Related videos on Youtube

Graeme
Author by

Graeme

Java Enterprise / Android Developer

Updated on July 09, 2022

Comments

  • Graeme
    Graeme almost 2 years

    I'm trying a simple app to read in the HTML of a website, and tranform it to create an easily readable UI in Android (This is an exersize in learning android, not to make a useable app). The problem I'm having is persisting a users session across Activities and then using the session in a HttpClient once recalled.

    I would like to do this "Correctly", the recommended approach seem to be to use CookieManager. I've had problems with this however - I cannot seem to find the "Correct" way to take a Cookie from the CookieManager and use it in a later instantiation of HttpClient in a seperate Activities.

    When using a CookieManager I can save the Cookie and the Cookie is then in scope in other Activities (See code snippet 2). I haven't found how to use this later (See code snippet 3) when requesting a page.

    Enough talking, here is some code. First my login action and Cookie storage:

    private OnClickListener loginActionListener = new OnClickListener() 
    {
        public void onClick(View v) 
        {
            EditText usernameTextView = (EditText) findViewById(R.id.Username);
            EditText passwordTextView = (EditText) findViewById(R.id.Password);
            String username = usernameTextView.getText().toString();
            String password = passwordTextView.getText().toString();
    
            try {
                HttpPost postMethod = new HttpPost(URI);                
                HttpParams params   = new BasicHttpParams();
    
                params.setParameter("mode", "login");
                params.setParameter("autologin", true);
                params.setParameter("username", username);
                params.setParameter("password", password);
                postMethod.setParams(params);
    
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpResponse response        = httpClient.execute(postMethod);
                List<Cookie> cookies = httpClient.getCookieStore().getCookies();
    
                if(cookies != null)
                {
                    for(Cookie cookie : cookies)
                    {
                        String cookieString = cookie.getName() + "=" + cookie.getValue() + "; domain=" + cookie.getDomain();                        
                        CookieManager.getInstance().setCookie(cookie.getDomain(), cookieString);  
                    }
                }
                CookieSyncManager.getInstance().sync();
    
                Intent intent = new Intent(v.getContext(), IndexAction.class);
                startActivity(intent);
        } catch (Exception e) {...}
    }
    

    The startup Activity which decides wether to make the user login or go to the index is below. You can see from this code that the cookie is in scope and can be read:

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CookieSyncManager.createInstance(this);
    
        if(CookieManager.getInstance().getCookie(URI) == null)
        {
            Intent intent = new Intent(this, LoginAction.class);
            startActivity(intent);
        }
        else
        {
            Intent intent = new Intent(this, IndexAction.class);
            startActivity(intent);
        }
    }
    

    But from my code to read the Index page I'm hoping you can suggest what i'm missing:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CookieSyncManager.createInstance(this);
    
        try
        {
                HttpGet getMethod = new HttpGet(URI_INDEX);  
    
                HttpParams params   = new BasicHttpParams();                        
                HttpConnectionParams.setConnectionTimeout(params, 30000);
                HttpConnectionParams.setSoTimeout(params, 30000);
    
                // This code results in a ClassCastException, I'm assuming i've found a red herring with this solution.
                // HttpContext localContext = new BasicHttpContext();    
                // localContext.setAttribute(ClientContext.COOKIE_STORE, CookieManager.getInstance().getCookie(URI));
    
                DefaultHttpClient httpClient = new DefaultHttpClient(params);
                HttpResponse response        = httpClient.execute(getMethod);
    
                if(response.getStatusLine().getStatusCode() > 299 && response.getStatusLine().getStatusCode() < 400)
                {
                    // Not logged in doesn't give a redirect response. Very annoying.
                }
    
                final char[] buffer = new char[0x10000];
                StringBuilder out = new StringBuilder();
                Reader in = new InputStreamReader(response.getEntity().getContent(), "UTF-8");
                int read = 0;
                while (read>=0)
                {
                  read = in.read(buffer, 0, buffer.length);
                  if (read>0) {
                    out.append(buffer, 0, read);
                  }
                }
    
                String returnString = out.toString();
        } catch (ClientProtocolException e) {...}
    }
    

    The HttpClient on execute(getMethod) isn't using the Cookie (double checked this in debug) to pull back the page. It would be great if someone could fill this hole in my knowledge.

    Thanks in advance.

    EDIT

    When commented code is added back in (with the httpClient.execute(getMethod) method change to httpClient.execute(getMethod, localContext)) this strack trace is produced - Assumedly because i'm filling the attribute ClientContext.COOKIE_STORE with a Cookie String rather than a CookieStore:

    *org.apache.http.client.protocol.RequestAddCookies.process(RequestAddCookies.java:88), org.apache.http.protocol.BasicHttpProcessor.process(BasicHttpProcessor.java:290), org.apache.http.protocol.HttpRequestExecutor.preProcess(HttpRequestExecutor.java:160), org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:401)
    org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555), org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487), 
    com.testapp.site.name.IndexAction.onCreate(IndexAction.java:47), 
    android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047), 
    android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611), 
    android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663), 
    android.app.ActivityThread.access$1500(ActivityThread.java:117), 
    android.app.ActivityThread$H.handleMessage(ActivityThread.java:931), 
    android.os.Handler.dispatchMessage(Handler.java:99), 
    android.os.Looper.loop(Looper.java:123), 
    android.app.ActivityThread.main(ActivityThread.java:3683), 
    java.lang.reflect.Method.invokeNative(Native Method), 
    java.lang.reflect.Method.invoke(Method.java:507), 
    com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839), 
    com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597), 
    dalvik.system.NativeStart.main(Native Method)*
    
    • Michael
      Michael almost 13 years
      Could you provide the stack trace of your exception? This question may help: stackoverflow.com/questions/1652850/…
    • Michael
      Michael almost 13 years
    • Graeme
      Graeme almost 13 years
      It doesn't help that httpClient.getCookieStore().addCookie(Cookie) needs a Cookie object and CookieManager.getInstance().getCookie(URI) returns a string. Cookie is an Interface and I don't know what subtype to use.
  • Graeme
    Graeme almost 13 years
    I specifically wanted to be able to pass only what is needed around my application (Although, example code on /how/ to pass this around would be well received). In this case the only thing that i needed in each Activity is the Cookie. The recommended approach to using Cookies across Activities (and also inter-process from what i've read) is to use the CookieManager which is why I wanted to use it to both save, view and then reuse Cookies inside of it. I will post my own solution to this problem ASAP and hopefully have any weaknesses in it explained.
  • Graeme
    Graeme almost 13 years
    I will also look into changing this code to use Java's internal HttpClient and post the solution along with any advantages i see in it.
  • Simon
    Simon almost 12 years
    its important to also set the domain of the new BasicClientCookie(key, value) so better something like BasicClientCookie c = new BasicClientCookie(key, value); c.setDomain(URI_FOR_DOMAIN); httpClient.getCookieStore().addCookie(c);
  • jlengrand
    jlengrand over 10 years
    This answer is just awesome
  • Shreyash Mahajan
    Shreyash Mahajan over 10 years
    Where is SerializableCoockie.class file
  • Shreyash Mahajan
    Shreyash Mahajan over 10 years
    I am not getting file. Please send me the Link for it. I really need it.
  • AndroidCoolestRulest
    AndroidCoolestRulest over 10 years
    You can download the SerializableCoockie.class from here
  • kiltek
    kiltek about 10 years
    Thank you for comparing "recommended approaches" with "custom approaches".
  • yati sagade
    yati sagade about 10 years
    This saved me some hair. Also, I'd love to know if you've managed to find an even better way since you posted this. Thanks :)
  • Francisco Corrales Morales
    Francisco Corrales Morales almost 10 years
    I get: The method getCookieStore() is undefined for the type HttpClient
  • Graeme
    Graeme almost 10 years
    Note that it's a DefaultHttpClient
  • Rahul Rastogi
    Rahul Rastogi almost 10 years
    Asynchronous request can not be done with single instance of HttpClient class
  • rmpt
    rmpt over 5 years
    I'm developing a brand new android app and will use your first approach - share the same HttpClient. Will have a static HttpClient on a custom BaseActivity and all my activities will extend BaseActivity. This way I have the same HttpClient for all screens. Thanks