I need to access some web pages and pass cookies around the way browsers do. This is easily done using
CookieHandler.setDefault(new MyCookieManager());
but this introduces global state which I need to avoid (Imagine accessing two accounts on the same server concurrently). So what I'd like to do is something like
String doGetWithCookies(URL url, MyCookies myCookies) {
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
myCookies.addToRequest(...);
myCookies.updateFromResponse(...);
return getHttpBody(conn);
}
but I can't see how to do it. The methods CookieManager.get
and put
do accept an URL
, but I want to use
- the same cookies with different
URL
s - different cookies for the same
URL
for different accounts
What I've tried: Nothing as there are just four methods available and a single subclass and nothing fits. Parsing the headers manually is surely doable but IMHO no option in 2014. I know about the Apache Http Client, but 1. I'd hope something that trivial needs no half a megabyte library, 2. at the first glance I can't see a solution there either.
Clarification:
Imagine you want to lock to SO as two different users. You can do it by using two computers or two different browsers (Chrome and Firefox) on a single computer. You can't do it in two tabs of a single browser.
What I want is equivalent to the possibility of simulating two browser. In the meantime I've found a related question and posted hacky solution to it.
Still I'm looking for an explanation behind the CookieHandler
design.
How are you sending your requests to the server?
If you're using HttpUrlConnection, then you could implement your own cookies. Every time you make a request, add your
Cookie:foo=1;bar=2
header to the request. Every time you read a response, check for aCookie
header and store it for subsequent requests.One tricky part will be knowing when to send a cookie, based on the URL being requested.
There's nothing built-in to the JDK to do this besides CookieHandler, which is lacking.
HttpClient might be a good option, as it has support for granular cookies.
The fundamental problem here, as you mentioned is that
introduces a global state. Why Oracle failed to provide a simple means of managing cookies on a per session basis is beyond me.
Here's how you can do it, though.
URLConnectionCookieManager
class with two methods:setCookiesFromCookieJar(urlConnection)
andputCookiesInCookieJar(urlConnection)
.URLConnectionCookieManager
class. (Note that its methods are NOT static; they are instance methods.)urlConnectionCookieManager.setCookiesFromCookieJar(urlConnection)
. This method retrieves cookies from the "cookie jar" and adds them to the connection's request headers.urlConnectionCookieManager.putCookiesInCookieJar(urlConnection)
. This method retrieves cookies from the connection and puts them in the "cookie jar".Make sure you use the instance of
URLConnectionCookieManager
that corresponds to the "session".The URLConnectionCookieManager class looks like this:
I actually like your "hacky" solution if you are working with multiple threads and you can guarantee that the same thread is responsible for accessing resources on behalf of the same user. If that is not the case, i.e. two or more threads need to share a user account and the corresponding cookies or a single thread needs to access resources for multiple users, this solution will probably not work...
My first attempt would have been to set an individual
CookieManager
for every user account:This is pretty similar to your solution except that it does not rely on the one-user-per-thread assumption. However, locking the
CookieManager
the entire time isn't probably what you want. But as it turns out, theHttpURLConnection
saves a reference to the defaultCookieManager
in its constructor. Exploiting this, we get what I would really call a hacky solution:Now we "only" lock the
CookieManager
for the connection setup which will probably increase concurrency. But it's still pretty ugly and you would now have to make sure that you lock theCookieManager
if you make requests from somewhere else that should not use user-specific cookies...I skimmed the source code of
HttpURLConnection
andHttpClient
to see when it actually retrieves and stores cookies. Apparently, the only place where theCookieManager
is queried for the cookies that should be sent is in the privatesetCookieHeader
method, which is called by bothgetOutputStream
andgetInputStream
before the request is sent.setCookieHeader
passes the current request headers to the installedCookieManager
. Maybe we could use these headers instead?This one retrieves user-specific cookies if there is an application-defined request header
X-Username
and gets a common set of cookies if no such header exists. However, updating the user-specific cookies won't be this easy because the server will most certainly not send theX-Username
header back to us. The idea would of course be to somehow determine the username from theresponseHeaders
. But I currently don't see a way how to inject the correct header field in the server`s response without setting up a proxy server.... Sorry :(FYI: The only place I could find that calls
CookieManager.put
is the privateparseHTTPHeader
method inHttpClient
. This method is of course invoked fromgetInputStream
before you can read the response body.In that case you need to hack the request reader so it won't use the standard "sessionId" parameter but your own "tabSessionId". This should be done by overriding the header parsing method. Try overriding java.net.URLConnection.getHeaderField(int) to use your own cookie when "sessionId" is requested