Using cookies with Android volley library
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.
Solution 1:
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:
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:
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 Should HttpURLConnection with CookieManager automatically handle session cookies?
Solution 4:
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:
@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);