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:
String doGetForUser(URL url, String username) {
synchronized (...) {
CookieManager.setDefault(getCookieManagerForUser(username));
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// Calling getInputStream on the connection automatically retrieves
// cookies from the CookieManager and stores new cookies that have been
// sent from the server.
return getHttpBody(conn);
}
}
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, the HttpURLConnection
saves a reference to the default CookieManager
in its constructor. Exploiting this, we get what I would really call a hacky solution:
String doGetForUser(URL url, String username) {
HttpsUrlConnection conn;
synchronized (...) {
CookieManager.setDefault(getCookieManagerForUser(username));
// this saves the current CookieManager
conn = (HttpsURLConnection) url.openConnection();
CookieManager.setDefault( ... ); // restore original one?
}
return getHttpBody(conn);
}
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 the CookieManager
if you make requests from somewhere else that should not use user-specific cookies...
I skimmed the source code of HttpURLConnection
and HttpClient
to see when it actually retrieves and stores cookies. Apparently, the only place where the CookieManager
is queried for the cookies that should be sent is in the private setCookieHeader
method, which is called by both getOutputStream
and getInputStream
before the request is sent. setCookieHeader
passes the current request headers to the installed CookieManager
. Maybe we could use these headers instead?
String doGetForUser(URL url, String username) {
HttpURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestProperty("X-Username", username);
return getHttpBody(conn);
}
class UsernameCookieHandler extends CookieHandler {
@Override
public Map<String, List<String>>get(URI uri, Map<String, List<String>> requestHeaders) throws IOException {
if (requestHeaders.containsKey("X-Username"))
return getUserSpecificCookies(uri, requestHeaders);
else
return getCommonCookies(uri, requestHeaders);
}
private Map<String, List<String>> getUserSpecificCookies(URI uri, Map<String, List<String>> requestHeaders) {
// evaluate X-Username and get cookies from a special CookieStore or so...
}
private Map<String, List<String>> getCommonCookies(URI uri, Map<String, List<String>> requestHeaders) {
// get cookies from a common CookieStore...
}
@Override
public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
// ???
}
}
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 the X-Username
header back to us. The idea would of course be to somehow determine the username from the responseHeaders
. 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 private parseHTTPHeader
method in HttpClient
. This method is of course invoked from getInputStream
before you can read the response body.