Using cookies with Android volley library

AndroidAndroid Volley

Android Problem Overview


Does anybody know how to attach a session cookie to the request using com.android.volley library? When I log in to a web site it gives me a session cookie. Browser would send that cookie back with any subsequent request. Volley does not seem to do that, at least not automatically.

Thanks.

Android Solutions


Solution 1 - Android

Volley doesn't actually make HTTP requests itself, and thus doesn't manage Cookies directly. It instead uses an instance of HttpStack to do this. There are two main implementations:

  • HurlStack: Uses HttpUrlConnection under the hood
  • HttpClientStack: uses Apache HttpClient under the hood

Cookie management is the responsibility of those HttpStacks. And they each handle Cookies differently.

If you need to support < 2.3, then you should use the HttpClientStack:

Configure an HttpClient instance, and pass that to Volley for it to use under the hood:

// If you need to directly manipulate cookies later on, hold onto this client
// object as it gives you access to the Cookie Store
DefaultHttpClient httpclient = new DefaultHttpClient();

CookieStore cookieStore = new BasicCookieStore();
httpclient.setCookieStore( cookieStore );

HttpStack httpStack = new HttpClientStack( httpclient );
RequestQueue requestQueue = Volley.newRequestQueue( context, httpStack  );

The advantage with this vs manually inserting cookies into the headers is that you get actual cookie management. Cookies in your store will properly respond to HTTP controls that expire or update them.

I've gone a step further and sub-classed BasicCookieStore so that I can automatically persist my cookies to disk.

HOWEVER! If you don't need to support older versions of Android. Just use this method:

// CookieStore is just an interface, you can implement it and do things like
// save the cookies to disk or what ever.
CookieStore cookieStore = new MyCookieStore();
CookieManager manager = new CookieManager( cookieStore, CookiePolicy.ACCEPT_ALL );
CookieHandler.setDefault( manager  );

// Optionally, you can just use the default CookieManager
CookieManager manager = new CookieManager();
CookieHandler.setDefault( manager  );

HttpURLConnection will query the CookieManager from that implicitly. HttpUrlConnection is also more performant and a bit cleaner to implement and work with IMO.

Solution 2 - Android

vmirinov is right!

Here is how I solved the problem:

Request class:

public class StringRequest extends com.android.volley.toolbox.StringRequest {

    private final Map<String, String> _params;

    /**
     * @param method
     * @param url
     * @param params
     *            A {@link HashMap} to post with the request. Null is allowed
     *            and indicates no parameters will be posted along with request.
     * @param listener
     * @param errorListener
     */
    public StringRequest(int method, String url, Map<String, String> params, Listener<String> listener,
		    ErrorListener errorListener) {
	    super(method, url, listener, errorListener);
	
	    _params = params;
    }

    @Override
    protected Map<String, String> getParams() {
	    return _params;
    }

    /* (non-Javadoc)
     * @see com.android.volley.toolbox.StringRequest#parseNetworkResponse(com.android.volley.NetworkResponse)
     */
    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
	    // since we don't know which of the two underlying network vehicles
	    // will Volley use, we have to handle and store session cookies manually
	    MyApp.get().checkSessionCookie(response.headers);
	
	    return super.parseNetworkResponse(response);
    }

    /* (non-Javadoc)
     * @see com.android.volley.Request#getHeaders()
     */
    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
	    Map<String, String> headers = super.getHeaders();
	
	    if (headers == null
			    || headers.equals(Collections.emptyMap())) {
		    headers = new HashMap<String, String>();
	    }
	
	    MyApp.get().addSessionCookie(headers);
	
	    return headers;
    }
}

and MyApp:

public class MyApp extends Application {
    private static final String SET_COOKIE_KEY = "Set-Cookie";
    private static final String COOKIE_KEY = "Cookie";
    private static final String SESSION_COOKIE = "sessionid";

    private static MyApp _instance;
  private RequestQueue _requestQueue;
  private SharedPreferences _preferences;

    public static MyApp get() {
        return _instance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        _instance = this;
	        _preferences = PreferenceManager.getDefaultSharedPreferences(this);
        _requestQueue = Volley.newRequestQueue(this);
    }

    public RequestQueue getRequestQueue() {
        return _requestQueue;
    }
    

    /**
     * Checks the response headers for session cookie and saves it
     * if it finds it.
     * @param headers Response Headers.
     */
    public final void checkSessionCookie(Map<String, String> headers) {
	    if (headers.containsKey(SET_COOKIE_KEY)
			    && headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) {
			    String cookie = headers.get(SET_COOKIE_KEY);
			    if (cookie.length() > 0) {
				    String[] splitCookie = cookie.split(";");
				    String[] splitSessionId = splitCookie[0].split("=");
				    cookie = splitSessionId[1];
				    Editor prefEditor = _preferences.edit();
				    prefEditor.putString(SESSION_COOKIE, cookie);
				    prefEditor.commit();
			    }
		    }
    }

    /**
     * Adds session cookie to headers if exists.
     * @param headers
     */
    public final void addSessionCookie(Map<String, String> headers) {
	    String sessionId = _preferences.getString(SESSION_COOKIE, "");
	    if (sessionId.length() > 0) {
		    StringBuilder builder = new StringBuilder();
		    builder.append(SESSION_COOKIE);
		    builder.append("=");
		    builder.append(sessionId);
		    if (headers.containsKey(COOKIE_KEY)) {
			    builder.append("; ");
			    builder.append(headers.get(COOKIE_KEY));
		    }
		    headers.put(COOKIE_KEY, builder.toString());
	    }
    }
    
}

Solution 3 - Android

The default HTTP transport code for Volley is HttpUrlConnection. If I am reading the documentation correctly, you need to opt into automatic session cookie support:

CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);

See also https://stackoverflow.com/questions/14860087/should-httpurlconnection-with-cookiemanager-automatically-handle-session-cookies

Solution 4 - Android

Guys try this in onCreate method of your AppController.java

  CookieHandler.setDefault(new CookieManager());

Hope it'll save time of developers. I have wasted four hours in debugging and searching appropriate solution.

Solution 5 - Android

@Rastio solution doesn't work if there are multiple 'Set-Cookie' headers. I wrapped the default CookieManager cookie store and before adding a cookie I saved it in SharedPreferences using Gson to serialize the cookie.

This is an example of the cookie store wrapper:

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import com.google.gson.Gson;

import java.net.CookieManager;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;

/**
 * Class that implements CookieStore interface. This class saves to SharedPreferences the session
 * cookie.
 *
 * Created by lukas.
 */
public class PersistentCookieStore implements CookieStore {

    private CookieStore mStore;
    private Context mContext;
    private Gson mGson;

    public PersistentCookieStore(Context context) {
        // prevent context leaking by getting the application context
        mContext = context.getApplicationContext();
        mGson = new Gson();

        // get the default in memory store and if there is a cookie stored in shared preferences,
        // we added it to the cookie store
        mStore = new CookieManager().getCookieStore();
        String jsonSessionCookie = Prefs.getJsonSessionCookie(mContext);
        if (!jsonSessionCookie.equals(Prefs.DEFAULT_STRING)) {
            HttpCookie cookie = mGson.fromJson(jsonSessionCookie, HttpCookie.class);
            mStore.add(URI.create(cookie.getDomain()), cookie);
        }
    }

    @Override
    public void add(URI uri, HttpCookie cookie) {
        if (cookie.getName().equals("sessionid")) {
            // if the cookie that the cookie store attempt to add is a session cookie,
            // we remove the older cookie and save the new one in shared preferences
            remove(URI.create(cookie.getDomain()), cookie);
            Prefs.saveJsonSessionCookie(mContext, mGson.toJson(cookie));
        }

        mStore.add(URI.create(cookie.getDomain()), cookie);
    }

    @Override
    public List<HttpCookie> get(URI uri) {
        return mStore.get(uri);
    }

    @Override
    public List<HttpCookie> getCookies() {
        return mStore.getCookies();
    }

    @Override
    public List<URI> getURIs() {
        return mStore.getURIs();
    }

    @Override
    public boolean remove(URI uri, HttpCookie cookie) {
        return mStore.remove(uri, cookie);
    }

    @Override
    public boolean removeAll() {
        return mStore.removeAll();
    }
}

Then, to use the cookie store just set in the CookieManager and that's it!

CookieManager cookieManager = new CookieManager(new PersistentCookieStore(mContext),
    CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);

Solution 6 - Android

I know the post and a little old, but we went through this recent problem, we need to share the session of a logged User between servers, and server side solution began to require a value to be provided by the client side, through cookie. One solution we found was to add a parameter to RequestQueue object, the code snippet in the method getRequestQueue before instantiating the RequestQueue found on the link below, and solve the problem, not sure how, but it started to work.

Visit http://woxiangbo.iteye.com/blog/1769122

public class App extends Application {

    public static final String TAG = App.class.getSimpleName();

    private static App         mInstance;

    public static synchronized App getInstance() {
        return App.mInstance;
    }

    private RequestQueue mRequestQueue;

    public <T> void addToRequestQueue( final Request<T> req ) {
        req.setTag( App.TAG );
        this.getRequestQueue().add( req );
    }

    public <T> void addToRequestQueue( final Request<T> req, final String tag ) {
        req.setTag( TextUtils.isEmpty( tag ) ? App.TAG : tag );
        this.getRequestQueue().add( req );
    }

    public void cancelPendingRequests( final Object tag ) {
        if ( this.mRequestQueue != null ) {
            this.mRequestQueue.cancelAll( tag );
        }
    }

    public RequestQueue getRequestQueue() {

        if ( this.mRequestQueue == null ) {

        
            DefaultHttpClient mDefaultHttpClient = new DefaultHttpClient();

            final ClientConnectionManager mClientConnectionManager = mDefaultHttpClient.getConnectionManager();
            final HttpParams mHttpParams = mDefaultHttpClient.getParams();
            final ThreadSafeClientConnManager mThreadSafeClientConnManager = new ThreadSafeClientConnManager( mHttpParams, mClientConnectionManager.getSchemeRegistry() );

            mDefaultHttpClient = new DefaultHttpClient( mThreadSafeClientConnManager, mHttpParams );

            final HttpStack httpStack = new HttpClientStack( mDefaultHttpClient );

            this.mRequestQueue = Volley.newRequestQueue( this.getApplicationContext(), httpStack );
        }

        return this.mRequestQueue;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        App.mInstance = this;
    }
}

//set token value

ObjectRequest.setHeader( "Cookie", "JSESSIONID=" + tokenValueHere );

Solution 7 - Android

Use this method to use Volley with cookies to:

  1. Use only very well tested code licenced under Apache 2 license
  2. Make as many requests as you'd like at the same time
  3. Make sure cookies persist on the device
  4. Not having to reinvent the wheel

My server uses cookies to authenticate and obviously I wanted to ensure that cookies persist on the device. So my solution was to use PersistentCookieStore and SerializableCookie classes from Asynchronous Http Client for Android.

First, in order to enable concurrent requests, an Apache HttpClient v4.3 port for Android is needed - one that comes with the system is outdated. More info here. I use Gradle, so this is how I imported it:

dependencies {
    compile group: 'org.apache.httpcomponents' , name: 'httpclient-android' , version: '4.3.3'
}

Function to get RequestQueue (in my class that extends Application):

private RequestQueue mRequestQueue;
private CloseableHttpClient httpClient;

...

public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
        httpClient = HttpClients.custom()
            .setConnectionManager(new PoolingHttpClientConnectionManager())
            .setDefaultCookieStore(new PersistentCookieStore(getApplicationContext()))
            .build();
        mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HttpClientStack(httpClient));
    }
    return mRequestQueue;
}

This is how I queue up a Request

public <T> void addToRequestQueue(Request<T> req, String tag) {
    req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
    getRequestQueue().add(req);
}

That's it!

Solution 8 - Android

gingerbread+ Android Versions:

there is another easy way to maintain cookies session and that is to add this line in a class that is extended with APPLICATION class:

CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));

Solution 9 - Android

If you have already started implementing your application by using the Loopj library, you will notice that you can't use new HttpClient instance in Volley.newRequestQUeue() because you will get various errors about not closing your previous connection etc.

Errors like:

java.lang.IllegalStateException: No wrapped connection

Invalid use of SingleClientConnManager: connection still allocated.

Now sometimes it takes time to refactor all your old API calls and rewrite them using volley, but you can use volley and loopj at the same time and share a cookiestore between those two until you write everything in volley (use volley instead of loopj, it is much better :) ).

This is how you can share HttpClient and CookieStore from loopj with volley.

// For example you initialize loopj first
private static AsyncHttpClient client = new AsyncHttpClient();
sCookieStore = new PersistentCookieStore(getSomeContextHere());
client.setTimeout(DEFAULT_TIMEOUT);
client.setMaxConnections(12);
client.setCookieStore(sCookieStore);
client.setThreadPool(((ThreadPoolExecutor) Executors.newCachedThreadPool()));

public static RequestQueue getRequestQueue(){
    if(mRequestQueue == null){
		
    HttpClient httpclient = KkstrRestClient.getClient().getHttpClient();
		
    ((AbstractHttpClient) httpclient).setCookieStore( ApplicationController.getCookieStore() );
		
    HttpStack httpStack = new HttpClientStack(httpclient);
		
    mRequestQueue = Volley.newRequestQueue(getContext(), httpStack);
    }
	
    return mRequestQueue;
}

This happened to me, we started using loopj. After 50 000 lines of code and discovery that loopj doesn't always work like expected we decided to switch to Volley.

Solution 10 - Android

The answer of @CommonsWare is the one I'd use. However, it looks like KitKat is having some bugs when that's done (When you create a CookieManager with custom CookieStore which you need if you want persistent Cookies). Given the fact that regardless of the implementation of the CookieStore that is used, Volley would throw a NullpointerException, I had to create my own CookieHandler... use it if you find it helpful.

public class MyCookieHandler extends CookieHandler {

private static final String VERSION_ZERO_HEADER = "Set-cookie";

private static final String VERSION_ONE_HEADER = "Set-cookie2";
private static final String COOKIE_HEADER = "Cookie";

private static final String COOKIE_FILE = "Cookies";
private Map<String, Map<String, HttpCookie>> urisMap;

private Context context;

public MyCookieHandler(Context context) {

	this.context = context;
	loadCookies();

}

@SuppressWarnings("unchecked")
private void loadCookies() {
	File file = context.getFileStreamPath(COOKIE_FILE);
	if (file.exists())
		try {

			FileInputStream fis = context.openFileInput(COOKIE_FILE);
			BufferedReader br = new BufferedReader(new InputStreamReader(
					fis));
			String line = br.readLine();
			StringBuilder sb = new StringBuilder();
			while (line != null) {
				sb.append(line);
				line = br.readLine();
			}
			Log.d("MyCookieHandler.loadCookies", sb.toString());
			JSONObject jsonuris = new JSONObject(sb.toString());
			urisMap = new HashMap<String, Map<String, HttpCookie>>();
			Iterator<String> jsonurisiter = jsonuris.keys();

			while (jsonurisiter.hasNext()) {
				String prop = jsonurisiter.next();
				HashMap<String, HttpCookie> cookiesMap = new HashMap<String, HttpCookie>();
				JSONObject jsoncookies = jsonuris.getJSONObject(prop);
				Iterator<String> jsoncookiesiter = jsoncookies.keys();
				while (jsoncookiesiter.hasNext()) {
					String pprop = jsoncookiesiter.next();
					cookiesMap.put(pprop,
							jsonToCookie(jsoncookies.getJSONObject(pprop)));
				}
				urisMap.put(prop, cookiesMap);

			}

		} catch (Exception e) {

			e.printStackTrace();
		}
	else {
		urisMap = new HashMap<String, Map<String, HttpCookie>>();
	}
}

@Override
public Map<String, List<String>> get(URI arg0,
		Map<String, List<String>> arg1) throws IOException {
	Log.d("MyCookieHandler.get",
			"getting Cookies for domain: " + arg0.getHost());
	Map<String, HttpCookie> cookies = urisMap.get(arg0.getHost());
	if (cookies != null)
		for (Entry<String, HttpCookie> cookie : cookies.entrySet()) {
			if (cookie.getValue().hasExpired()) {
				cookies.remove(cookie.getKey());
			}
		}

	if (cookies == null || cookies.isEmpty()) {
		Log.d("MyCookieHandler.get", "======");
		return Collections.emptyMap();
	}
	Log.d("MyCookieHandler.get",
			"Cookie : " + TextUtils.join("; ", cookies.values()));
	Log.d("MyCookieHandler.get", "======");
	return Collections.singletonMap(COOKIE_HEADER, Collections
			.singletonList(TextUtils.join("; ", cookies.values())));
}

@Override
public void put(URI uri, Map<String, List<String>> arg1) throws IOException {
	Map<String, HttpCookie> cookies = parseCookies(arg1);
	Log.d("MyCookieHandler.put",
			"saving Cookies for domain: " + uri.getHost());

	addCookies(uri, cookies);
	Log.d("MyCookieHandler.put",
			"Cookie : " + TextUtils.join("; ", cookies.values()));
	Log.d("MyCookieHandler.put", "======");

}

private void addCookies(URI uri, Map<String, HttpCookie> cookies) {
	if (!cookies.isEmpty()) {
		if (urisMap.get(uri.getHost()) == null) {
			urisMap.put(uri.getHost(), cookies);
		} else {
			urisMap.get(uri.getHost()).putAll(cookies);
		}
		saveCookies();
	}
}

private void saveCookies() {
	try {
		FileOutputStream fos = context.openFileOutput(COOKIE_FILE,
				Context.MODE_PRIVATE);

		JSONObject jsonuris = new JSONObject();
		for (Entry<String, Map<String, HttpCookie>> uris : urisMap
				.entrySet()) {
			JSONObject jsoncookies = new JSONObject();
			for (Entry<String, HttpCookie> savedCookies : uris.getValue()
					.entrySet()) {
				jsoncookies.put(savedCookies.getKey(),
						cookieToJson(savedCookies.getValue()));
			}
			jsonuris.put(uris.getKey(), jsoncookies);
		}
		fos.write(jsonuris.toString().getBytes());
		fos.close();
		Log.d("MyCookieHandler.addCookies", jsonuris.toString());
	} catch (Exception e) {
		e.printStackTrace();
	}
}

private static JSONObject cookieToJson(HttpCookie cookie) {
	JSONObject jsoncookie = new JSONObject();
	try {
		jsoncookie.put("discard", cookie.getDiscard());
		jsoncookie.put("maxAge", cookie.getMaxAge());
		jsoncookie.put("secure", cookie.getSecure());
		jsoncookie.put("version", cookie.getVersion());
		jsoncookie.put("comment", cookie.getComment());
		jsoncookie.put("commentURL", cookie.getCommentURL());
		jsoncookie.put("domain", cookie.getDomain());
		jsoncookie.put("name", cookie.getName());
		jsoncookie.put("path", cookie.getPath());
		jsoncookie.put("portlist", cookie.getPortlist());
		jsoncookie.put("value", cookie.getValue());

	} catch (JSONException e) {

		e.printStackTrace();
	}

	return jsoncookie;
}

private static HttpCookie jsonToCookie(JSONObject jsonObject) {
	HttpCookie httpCookie;
	try {
		httpCookie = new HttpCookie(jsonObject.getString("name"),
				jsonObject.getString("value"));
		if (jsonObject.has("comment"))
			httpCookie.setComment(jsonObject.getString("comment"));
		if (jsonObject.has("commentURL"))
			httpCookie.setCommentURL(jsonObject.getString("commentURL"));
		if (jsonObject.has("discard"))
			httpCookie.setDiscard(jsonObject.getBoolean("discard"));
		if (jsonObject.has("domain"))
			httpCookie.setDomain(jsonObject.getString("domain"));
		if (jsonObject.has("maxAge"))
			httpCookie.setMaxAge(jsonObject.getLong("maxAge"));
		if (jsonObject.has("path"))
			httpCookie.setPath(jsonObject.getString("path"));
		if (jsonObject.has("portlist"))
			httpCookie.setPortlist(jsonObject.getString("portlist"));
		if (jsonObject.has("secure"))
			httpCookie.setSecure(jsonObject.getBoolean("secure"));
		if (jsonObject.has("version"))
			httpCookie.setVersion(jsonObject.getInt("version"));
		return httpCookie;
	} catch (JSONException e) {

		e.printStackTrace();
	}
	return null;

}

private Map<String, HttpCookie> parseCookies(Map<String, List<String>> map) {
	Map<String, HttpCookie> response = new HashMap<String, HttpCookie>();

	for (Entry<String, List<String>> e : map.entrySet()) {
		String key = e.getKey();
		if (key != null
				&& (key.equalsIgnoreCase(VERSION_ONE_HEADER) || key
						.equalsIgnoreCase(VERSION_ZERO_HEADER))) {
			for (String cookie : e.getValue()) {
				try {
					for (HttpCookie htpc : HttpCookie.parse(cookie)) {
						response.put(htpc.getName(), htpc);
					}
				} catch (Exception e1) {

					Log.e("MyCookieHandler.parseCookies",
							"Error parsing cookies", e1);
				}
			}

		}
	}
	return response;

}
}

This answer hasn't been thoroughly tested. I used JSON to serialize Cookies, because well, that class don't implement Serializable and it's final.

Solution 11 - Android

In my project CookieManager is resolved as android.webkit.CookieManager. I have to set the handler like this below to make Volley auto handle cookies.

> CookieManager cookieManager = new java.net.CookieManager(); CookieHandler.setDefault(cookieManager);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRastioView Question on Stackoverflow
Solution 1 - AndroidAdamView Answer on Stackoverflow
Solution 2 - AndroidRastioView Answer on Stackoverflow
Solution 3 - AndroidCommonsWareView Answer on Stackoverflow
Solution 4 - AndroidAdnan Bin MustafaView Answer on Stackoverflow
Solution 5 - AndroidLukasView Answer on Stackoverflow
Solution 6 - AndroidAnderson KView Answer on Stackoverflow
Solution 7 - AndroidC0D3LIC1OU5View Answer on Stackoverflow
Solution 8 - AndroidasadullahView Answer on Stackoverflow
Solution 9 - AndroidDrag0View Answer on Stackoverflow
Solution 10 - AndroidAlejandro NavasView Answer on Stackoverflow
Solution 11 - Androidstarshine wangView Answer on Stackoverflow