如何制作CookieContainer的副本?

时间:2013-08-18 14:34:02

标签: c# .net cookies

鉴于,CookieContainer的实例为not thread safe

  

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

事实证明,如果没有同步,我不能在多个并发HTTP请求中使用相同的容器。不幸的是,MSDN的文档不清楚如何正确地同步它。

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

所以问题是:如何制作CookieContainer类实例的副本?

5 个答案:

答案 0 :(得分:8)

CookieContainer类是Serializable。既然你说你需要序列化它,为什么不只是使用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);
    }
}

答案 1 :(得分:4)

看看CookieContainter类,你会发现当cookie集合发生变化时会发生并发场景,对吗?

你会注意到CookieContainer的作者负责使用lock {}SyncRoot围绕代码的这些收集更改部分,我不认为这种方法没有得到解决并发场景。

此外,您可以注意到任何添加的Cookie实际上是克隆,因此容器内的Cookie和所有操作都不会搞乱cookie容器外的对象引用。在最糟糕的情况下,我错过了一些东西,克隆还给了我们一个关于你究竟要复制什么以及如何使用它的提示,如果使用其他帖子中描述的反射方法(我个人不会考虑)它是一个黑客,因为它符合要求,它是管理,合法和安全的代码:))。

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

答案 2 :(得分:2)

您可以使用Reflection获取与所有Uri相关的所有Cookie,然后创建新的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;
}

答案 3 :(得分: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"));

答案 4 :(得分:0)

只需在Alan's answer above上添加我自己的转折即可转换为Base64字符串。

moveProductFromWaitListToCart