【问题标题】:Puzzled by CookieHandler被 CookieHandler 困惑
【发布时间】:2015-07-01 06:12:56
【问题描述】:

我需要访问一些网页并以浏览器的方式传递 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,但我想使用

  • 相同的 cookie 具有不同的URLs
  • 同一URL 用于不同帐户的不同cookie

我尝试过的:什么都没有,因为只有四种可用的方法和一个子类,没有什么合适的。手动解析标头肯定是可行的,但恕我直言,2014 年没有选择。我知道Apache Http Client,但是 1. 我希望微不足道的东西不需要半兆字节的库,2. 乍一看我看不到那里也有解决方案。

澄清:

假设您想以两个不同的用户身份锁定 SO。您可以通过在一台计算机上使用两台计算机或两种不同的浏览器(Chrome 和 Firefox)来实现。您无法在单个浏览器的两个选项卡中执行此操作。

我想要的相当于模拟两个浏览器的可能性。与此同时,我发现了一个相关问题,并在上​​面发布了hacky solution

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

【问题讨论】:

  • 您能更好地解释您的需求吗?您是否希望在同一会话中同时连接多个用户? “不同帐户的相同 URL 的不同 cookie”是什么意思。从您拥有不同会话的那一刻起,情况总是如此(内部使用 cookie 来区分每个会话)
  • @ZiedHamdi:“内部使用 cookie 来区分每个会话:” - 是的,但前提是您有不同的 cookie。打开两个不同的浏览器并使用两个不同的帐户登录到 SO。有用。在单个浏览器的两个选项卡中尝试相同的操作。它不起作用(因为 cookie 是共享的)。我希望能够模拟多个浏览器。

标签: java http cookies


【解决方案1】:

您如何将请求发送到服务器?

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

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

除了缺少的 CookieHandler 之外,JDK 没有任何内置功能可以执行此操作。

HttpClient 可能是一个不错的选择,因为它支持granular cookies

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      如果您使用多个线程,我实际上很喜欢您的"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...

      我浏览了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<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 调用的,然后才能读取响应正文。

      【讨论】:

        【解决方案4】:

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

        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<String, List<String>> 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<String, List<String>> headerMap = new HashMap<String, List<String>>();
        
            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<Map.Entry<String, List<String>>> headerSet = headerMap.entrySet();
            Iterator<Map.Entry<String, List<String>>> headerIterator = headerSet.iterator();
            boolean hasNextPair = headerIterator.hasNext();
            while (hasNextPair)
              {
              Map.Entry<String, List<String>> pair = headerIterator.next();
              String key = pair.getKey();
              List<String> cookieList = pair.getValue();
        
              Iterator<String> 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_;
        
          }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-28
          • 2015-05-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多