CookieHandler困惑不解

时间:2014-05-03 17:10:47

标签: java http cookies

我需要访问一些网页并按照浏览器的方式传递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 s
  • 的相同Cookie
  • 针对不同帐户URL的不同Cookie

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

澄清:

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

我想要的就是模拟两个浏览器的可能性。与此同时,我发现了一个相关问题并向其发布了hacky solution

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

4 个答案:

答案 0 :(得分:0)

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

如果你正在使用HttpUrlConnection,那么你可以实现自己的cookie。每次发出请求时,都会在请求中添加Cookie:foo=1;bar=2标头。每次阅读回复时,请检查Cookie标题并将其存储以备后续请求。

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

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

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

如果您正在使用多个线程,我实际上喜欢您的"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调用此方法。

答案 3 :(得分:0)

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

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”。
  5. 确保使用与“会话”对应的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_;
    
      }