CookieHandler困惑不解

我需要访问一些网页并通过浏览器的方式传递cookie。 这很容易使用

CookieHandler.setDefault(new MyCookieManager()); 

但这引入了我需要避免的全局状态(想象一下同时访问同一服务器上的两个帐户)。 所以我想做的就像是

 String doGetWithCookies(URL url, MyCookies myCookies) { HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); myCookies.addToRequest(...); myCookies.updateFromResponse(...); return getHttpBody(conn); } 

但我看不出怎么做。 CookieManager.getput方法接受一个URL ,但我想使用

  • 具有不同URL的相同cookie
  • 不同帐户的相同URL不同Cookie

我尝试了什么:没有什么,因为只有四种方法可用而且只有一个子类,没有任何东西适合。 手动解析标题肯定是可行的,但IMHO在2014年没有选项。我知道Apache Http客户端 ,但是1.我希望一些微不足道的东西不需要半兆字节的库,2。乍一看我看不到那里有一个解决方案。

澄清:

想象一下,你想以两个不同的用户锁定SO。 您可以在一台计算机上使用两台计算机或两台不同的浏览器(Chrome和Firefox)来完成此操作。 您无法在单个浏览器的两个选项卡中执行此操作。

我想要的是相当于模拟两个浏览器的可能性。 与此同时,我发现了一个相关问题并发布了hacky解决方案 。

我仍在寻找CookieHandler设计背后的解释。

你是如何将请求发送到服务器的?

如果您正在使用HttpUrlConnection,那么您可以实现自己的cookie。 每次发出请求时,请将Cookie:foo=1;bar=2标头添加到请求中。 每次读取响应时,都要检查Cookie标头并将其存储为后续请求。

一个棘手的部分是根据请求的URL知道何时发送cookie。

除了缺少CookieHandler之外,JDK没有内置任何内容。

HttpClient可能是一个不错的选择,因为它支持粒度cookie 。

在这种情况下,您需要破解请求阅读器,以便它不会使用标准的“sessionId”参数,而是使用您自己的“tabSessionId”。 这应该通过覆盖头解析方法来完成。 尝试重写java.net.URLConnection.getHeaderField(int)以在请求“sessionId”时使用您自己的cookie

我实际上喜欢你的“hacky”解决方案,如果你正在使用多个线程,你可以保证同一个线程负责代表同一个用户访问资源。 如果不是这种情况,即两个或多个线程需要共享用户帐户并且相应的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); } } 

这非常类似于您的解决方案,除了它不依赖于每用户一个线程的假设。 但是,整个时间锁定CookieManager可能不是您想要的。 但事实certificate, HttpURLConnection在其构造函数中保存了对默认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

我浏览了HttpURLConnectionHttpClient的源代码,看看它实际检索和存储cookie的时间。 显然,查询CookieManager以查找应发送的cookie的唯一位置是私有setCookieHeader方法,该方法在发送请求之前由getOutputStreamgetInputStream调用。 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>get(URI uri, Map> requestHeaders) throws IOException { if (requestHeaders.containsKey("X-Username")) return getUserSpecificCookies(uri, requestHeaders); else return getCommonCookies(uri, requestHeaders); } private Map> getUserSpecificCookies(URI uri, Map> requestHeaders) { // evaluate X-Username and get cookies from a special CookieStore or so... } private Map> getCommonCookies(URI uri, Map> requestHeaders) { // get cookies from a common CookieStore... } @Override public void put(URI uri, Map> responseHeaders) throws IOException { // ??? } } 

如果存在应用程序定义的请求标头X-Username ,则此检索用户特定的cookie,如果不存在此标头,则获取一组公共cookie。 但是,更新用户特定的cookie并不容易,因为服务器肯定不会将X-Username标头发送回给我们。 这个想法当然是以某种方式确定来自responseHeaders的用户名。 但我目前没有看到如何在没有设置代理服务器的情况下在服务器响应中注入正确的头字段的方法….抱歉:(


仅供参考:唯一可以找到调用CookieManager.putHttpClient的私有parseHTTPHeader方法。 当然,在您可以读取响应主体之前,可以从getInputStream调用此方法。

正如你所提到的,这里的根本问题是

 CookieHandler.setDefault(new MyCookieManager()); 

介绍一个全球状态。 为什么Oracle无法提供基于每个会话管理cookie的简单方法,这超出了我的范围。

不过,这是你如何做到的。

  1. 包含一个URLConnectionCookieManager类,它有两个方法: setCookiesFromCookieJar(urlConnection)putCookiesInCookieJar(urlConnection)
  2. 对于每个单独的会话,创建URLConnectionCookieManager类的实例。 (请注意,它的方法不是静态的;它们是实例方法。)
  3. 在发送请求之前,请调用urlConnectionCookieManager.setCookiesFromCookieJar(urlConnection) 。 此方法从“cookie jar”中检索cookie,并将它们添加到连接的请求标头中。
  4. 发送请求后,调用urlConnectionCookieManager.putCookiesInCookieJar(urlConnection) 。 此方法从连接中检索cookie并将它们放在“cookie jar”中。

确保使用与“会话”对应的URLConnectionCookieManager实例。

URLConnectionCookieManager类如下所示:

 package http; import java.net.URLConnection; import java.net.CookieManager; import java.net.CookieHandler; import java.net.CookieStore; import java.net.CookiePolicy; import java.net.URL; import java.net.URI; import java.util.List; import java.util.Iterator; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.io.IOException; import java.io.Serializable; import java.net.URISyntaxException; public class URLConnectionCookieManager implements Serializable { public URLConnectionCookieManager() { this(null, null); } public URLConnectionCookieManager( CookieHandler cookieHandler) { setCookieHandler(cookieHandler); } public URLConnectionCookieManager( CookieStore cookieStore, CookiePolicy cookiePolicy) { CookieHandler cookieHandler = createCookieHandler(cookieStore, cookiePolicy); setCookieHandler(cookieHandler); } public void putCookiesInCookieJar( URLConnection urlConnection) throws IOException { Map> headers = urlConnection.getHeaderFields(); URL url = urlConnection.getURL(); URI uri = null; try { uri = url.toURI(); } catch (URISyntaxException urise) { System.out.println("Unable to convert URL to URI while putting cookies in cookie jar."); throw new IOException(urise); } CookieHandler cookieHandler = getCookieHandler(); cookieHandler.put(uri, headers); } public void setCookiesFromCookieJar( URLConnection urlConnection) throws IOException { Map> headerMap = new HashMap>(); URL url = urlConnection.getURL(); URI uri = null; try { uri = url.toURI(); } catch (URISyntaxException urise) { System.out.println("Unable to convert URL to URI while setting cookies from cookie jar."); throw new IOException(urise); } CookieHandler cookieHandler = getCookieHandler(); headerMap = cookieHandler.get(uri, headerMap); Set>> headerSet = headerMap.entrySet(); Iterator>> headerIterator = headerSet.iterator(); boolean hasNextPair = headerIterator.hasNext(); while (hasNextPair) { Map.Entry> pair = headerIterator.next(); String key = pair.getKey(); List cookieList = pair.getValue(); Iterator cookieIterator = cookieList.iterator(); boolean hasNextCookie = cookieIterator.hasNext(); while (hasNextCookie) { String cookie = cookieIterator.next(); urlConnection.addRequestProperty(key, cookie); hasNextCookie = cookieIterator.hasNext(); } hasNextPair = headerIterator.hasNext(); } } public CookieHandler getCookieHandler() { return this.cookieHandler_; } protected CookieHandler createCookieHandler( CookieStore cookieStore, CookiePolicy cookiePolicy) { CookieHandler cookieHandler = new CookieManager(cookieStore, cookiePolicy); return cookieHandler; } protected void setCookieHandler( CookieHandler cookieHandler) { this.cookieHandler_ = cookieHandler; } private CookieHandler cookieHandler_; }