2013-08-18 83 views
3

鉴于,CookieContainer的实例是not thread safe如何制作CookieContainer的副本?

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

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

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

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

+2

您是否看过使用Reflector或DotPeak的源代码?我相信CookieContainer是线程安全的,我已经使用过很多次,共享单个CookieContainer的许多Web服务请求,我从来没有遇到任何问题。 –

+0

我做了,我仍然需要能够复制它的序列化。 –

+0

http://stackoverflow.com/questions/15983166/how-can-i-get-all-cookies-of-a-cookiecontainer 我认为这符合您的要求。 –

回答

4

看看在CookieContainter类,你会看到,并发情景假设当有Cookie集合的变化发生,对不对?

您会注意到CookieContainer的作者负责在代码的这些集合更改部分使用lock {}SyncRoot,我不认为这种方法不适用于并发场景。此外,您可以注意到,任何添加的Cookie实际上都是克隆的,因此容器中的cookie以及所做的所有操作不会混淆cookie容器外的对象引用。在最糟糕的情况下,我错过了一些东西,克隆也给了我们一些建议,告诉我们你需要复制什么,以及如何使用其他帖子中描述的反射方法(我个人不会考虑它是一种破解,因为它符合要求,它是管理,合法和安全的代码:))。

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

2

您可以使用反射来获取相关的所有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; 
} 
+0

感谢您转发sombody的其他答案:http://stackoverflow.com/a/15991071/139667使用反射是一种破解。如果你看看内部的话,还有5个你没有提到的定义容器状态的字段。你也必须复制它们吗?如果CookieContainer类之外还有其他东西需要复制? –

+0

反射和序列化都是黑客,但我认为使用反射更常见和性能友好恕我直言! –

+0

序列化是一种合法的(批准的)破解。 –

6

的的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); 
    } 
} 
+0

@AmerSawan处理/关闭没有必要,因为MemoryStream不包含非托管资源。另外,我假设我的代码足以证明原理,并且代码的任何用户都足够聪明以适应自己的使用,并且如果他愿意,可以调用Dispose/Close。 – Alan

+0

因为'MemoryStream'实现了'IDisposable',所以你需要调用'Dispose'或者FXCop会报告代码的警告。 –

+0

这是一个有趣的转折。你试过了吗?它有用吗? –

1

你可以用反射来做到这一点。这或许可以得到改善,情况因人而异:

//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")); 
+0

使用反射是一种破解。如果你看看内部的话,还有5个你没有提到的定义容器状态的字段。你也必须复制它们吗?如果CookieContainer类之外还有其他东西需要复制? –

+0

这是一种黑客攻击,但无论出于什么原因,你试图做的事情都不是简单的事情,所以黑客就是你留下的。如果你反对反思,也许你可以提到这一点。其中3个内部映射到您可以通过赋值复制的属性(Capacity,MaxCookieSize和PerDomainCapacity)。计数是N/A,离开m_fqdnMyDomain,我对TBH不太确定 – wdavo

+1

Alan的序列化解决方案很可能是更好的选择 – wdavo