如果您使用多个线程,我实际上很喜欢您的"hacky" solution,并且您可以保证同一个线程代表同一个用户负责访问资源。如果不是这种情况,即两个或多个线程需要共享一个用户帐户和相应的 cookie,或者单个线程需要访问多个用户的资源,则此解决方案可能不起作用...
我的第一次尝试是为每个用户帐户设置一个单独的CookieManager:
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);
}
}
这与your solution 非常相似,只是它不依赖于每个线程一个用户的假设。但是,始终锁定CookieManager 可能不是您想要的。但事实证明,HttpURLConnection 在其constructor 中保存了对默认CookieManager 的引用。利用这一点,我们得到了我真正称之为 hacky 的解决方案:
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);
}
现在我们“只”为连接设置锁定CookieManager,这可能会增加并发性。但它仍然很丑陋,如果您从其他不应使用用户特定 cookie 的地方发出请求,您现在必须确保锁定 CookieManager...
我浏览了HttpURLConnection 和HttpClient 的源代码,看看它何时真正检索和存储cookie。显然,在CookieManager 中查询应该发送的cookie 的唯一位置是在私有setCookieHeader 方法中,该方法在发送请求之前由getOutputStream 和getInputStream 调用。 setCookieHeader 将当前请求标头传递给已安装的 CookieManager。也许我们可以改用这些标题?
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 {
// ???
}
}
如果存在应用程序定义的请求标头X-Username,则此检索特定于用户的cookie,如果不存在此类标头,则获取一组通用的cookie。但是,更新特定于用户的 cookie 不会那么容易,因为服务器肯定不会将 X-Username 标头发回给我们。这个想法当然是以某种方式从responseHeaders 中确定用户名。但是我目前看不到如何在不设置代理服务器的情况下在服务器响应中注入正确的标头字段....对不起:(
仅供参考:我能找到调用CookieManager.put 的唯一地方是HttpClient 中的私有parseHTTPHeader 方法。这个方法当然是从getInputStream 调用的,然后才能读取响应正文。