3

鉴于,CookieContainer 的实例不是线程安全的。

此类型的任何公共静态(在 Visual Basic 中为 Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。

所以事实证明我不能在没有同步的情况下跨多个并发 HTTP 请求使用同一个容器。不幸的是,从 MSDN 的文档中,不清楚如何正确同步它。

一个解决方案是为每个请求使用一个主容器的副本,一旦请求完成,来自副本的 cookie 可以合并回主容器。创建副本和合并可以同步方式完成。

所以问题是:如何复制 CookieContainer 类的实例?

4

5 回答 5

7

CookieContainer 类是可序列化的。既然您说无论如何都需要对其进行序列化,为什么不直接使用 BinaryFormatter 将其序列化为 MemorySteam,然后对其进行反序列化以进行复制呢?

我知道这太简单了,所以如果没有帮助请忽略。

private CookieContainer CopyContainer(CookieContainer container)
{
    using(MemoryStream stream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, container);
        stream.Seek(0, SeekOrigin.Begin);
        return (CookieContainer)formatter.Deserialize(stream);
    }
}
于 2013-08-21T00:24:09.810 回答
5

看一下CookieContainter类,您会发现当 cookie 集合发生变化时,假设会发生并发场景,对吧?

您会注意到 CookieContainer 的作者负责使用lock {}SyncRoot处理代码中这些更改集合的部分,我认为这种方法不适用于并发场景。

此外,您可以注意到任何添加的Cookie都是字面意义上的克隆,因此容器内的 cookie 以及所做的所有操作都不会与 cookie 容器外的对象引用混淆。在最坏的情况下,我遗漏了一些东西,克隆还为我们提供了一个提示,告诉我们你必须复制什么以及如何做到这一点,以防使用其他帖子中描述的反射方法(我个人不会考虑这是一个 hack,因为它符合要求并且它是受管理的、合法的和安全的代码 :) )。

事实上,MSDN 文档中都提到“任何实例成员都不能保证是线程安全的”。- 这是一种提醒,因为你是对的,你真的需要小心。然后有了这样的陈述,您基本上可以假设两件事:1)非静态成员根本不安全。2) 一些成员可以是线程安全的,但没有正确记录。

于 2013-08-22T01:21:21.707 回答
3

您可以使用 Reflection 来获取与 all 相关的所有 cookie Uri,然后创建新的CookieContainer并将它们添加到其中,例如这里:

public static CookieContainer DeepClone(CookieContainer src)
{
    CookieContainer cookieContainer = new CookieContainer();

    Hashtable table = (Hashtable)src.GetType().InvokeMember("m_domainTable", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, src, new object[] { });

    foreach (var tableKey in table.Keys)
    {
        String str_tableKey = (string)tableKey;

        if (str_tableKey[0] == '.')
            str_tableKey = str_tableKey.Substring(1);

        SortedList list = (SortedList)table[tableKey].GetType().InvokeMember("m_list", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance, null, table[tableKey], new object[] { });

        foreach (var listKey in list.Keys)
        {
            String url = "https://" + str_tableKey + (string)listKey;

            CookieCollection collection = src.GetCookies(new Uri(url));

            foreach (Cookie c in collection)
                cookieContainer.Add(new Cookie(c.Name, c.Value, c.Path, c.Domain)
                {
                    Comment = c.Comment,
                    CommentUri = c.CommentUri,
                    Discard = c.Discard,
                    Expired = c.Expired,
                    Expires = c.Expires,
                    HttpOnly = c.HttpOnly,
                    Port = c.Port,
                    Secure = c.Secure,
                    Version = c.Version
                });
        }
    }
    return cookieContainer;
}
于 2013-08-21T00:18:31.197 回答
1

你可以通过反射来做到这一点。这也许可以改进,YMMV:

//Set up the source cookie container
var cookieContainerA = new CookieContainer();
cookieContainerA.Add(new Uri("http://foobar.com"), new Cookie("foo", "bar"));
cookieContainerA.Add(new Uri("http://foobar.com"), new Cookie("baz", "qux"));
cookieContainerA.Add(new Uri("http://abc123.com"), new Cookie("abc", "123"));
cookieContainerA.Add(new Uri("http://abc123.com"), new Cookie("def", "456"));

//Set up our destination cookie container
var cookieContainerB = new CookieContainer();

//Get the domain table member
var type = typeof(CookieContainer);
var domainTableField = type.GetField("m_domainTable", BindingFlags.NonPublic | BindingFlags.Instance);
var domainTable = (Hashtable)domainTableField.GetValue(cookieContainerA);

//Iterate the domain table
foreach (var obj in domainTable)
{
  var entry = (DictionaryEntry)obj;

  //The domain is the key (we only need this for our Console.WriteLine later)
  var domain = entry.Key;
  var valuesProperty = entry.Value.GetType().GetProperty("Values");
  var values = (IList)valuesProperty.GetValue(entry.Value);

  foreach (var valueObj in values)
  {
    //valueObj is a CookieCollection, cast and add to our destination container
    var cookieCollection = (CookieCollection)valueObj;
    cookieContainerB.Add(cookieCollection);

    //This is a dump of our source cookie container
    foreach (var cookieObj in cookieCollection)
    {
      var cookie = (Cookie)cookieObj;
      Console.WriteLine("Domain={0}, Name={1}, Value={2}", domain, cookie.Name, cookie.Value);
    }
  }
}


//Test the copying
//var foobarCookies = cookieContainerB.GetCookies(new Uri("http://foobar.com"));
//var abc123Cookies = cookieContainerB.GetCookies(new Uri("http://abc123.com"));
于 2013-08-21T00:44:57.523 回答
0

只需在上面 Alan 的答案中添加我自己的转折,即可转换为 Base64 字符串/从 Base64 字符串转换。

public static string ToBase64(CookieContainer container)
{
    string str = null;

    byte[] bytes = null;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, container);
        bytes = ms.ToArray();
    }
    str = Convert.ToBase64String(bytes);

    return str;
}

public static CookieContainer FromBase64(string container_base64)
{
    CookieContainer cc = null;

    byte[] bytes = Convert.FromBase64String(container_base64);
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        BinaryFormatter bf = new BinaryFormatter();
        cc = (CookieContainer)bf.Deserialize(ms);
    }

    return cc;
}
于 2018-04-06T16:38:48.837 回答