2012-05-03 59 views
2

我的Delphi 2010应用上传东西使用多线程,上传的数据张贴到PHP/Web应用程序需要登录,所以我需要使用共享/全局Cookie管理器(我“M使用Indy10修订4743),因为TIdCookieManager不是线程安全:(全球,线程安全,饼干管理与Indy

此外,服务器端,会话ID是每5分钟自动重新生成,所以我必须保持同时全球&本地的cookie管理员同步。

我的代码如下所示:

TUploadThread = class(TThread) 
// ... 

var 
    GlobalCookieManager : TIdCookieManager; 

procedure TUploadThread.Upload(FileName : String); 
var 
    IdHTTP   : TIdHTTP; 
    TheSSL   : TIdSSLIOHandlerSocketOpenSSL; 
    TheCompressor : TIdCompressorZLib; 
    TheCookieManager : TIdCookieManager; 
    AStream   : TIdMultipartFormDataStream; 
begin 
    ACookieManager := TIdCookieManager.Create(IdHTTP); 

    // Automatically sync cookies between local & global Cookie managers 
    @TheCookieManager.OnNewCookie := pPointer(Cardinal(pPointer(procedure(ASender : TObject; ACookie : TIdCookie; var VAccept : Boolean) 
    begin 
      OmniLock.Acquire; 
      try 
      GlobalCookieManager.CookieCollection.AddCookie(ACookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL{IdHTTP.URL}); 
      finally 
        OmniLock.Release; 
      end; // try/finally 

      VAccept := True; 
    end)^) + $0C)^; 
    // ======================================== // 


    IdHTTP   := TIdHTTP.Create(nil); 
    with IdHTTP do 
    begin 
      HTTPOptions  := [hoForceEncodeParams, hoNoParseMetaHTTPEquiv]; 
      AllowCookies := True; 
      HandleRedirects := True; 
      ProtocolVersion := pv1_1; 

      IOHandler  := TheSSL; 
      Compressor  := TheCompressor; 
      CookieManager := TheCookieManager; 
    end; // with 

    OmniLock.Acquire; 
    try 
     // Load login info/cookies 
     TheCookieManager.CookieCollection.AddCookies(GlobalCookieManager.CookieCollection); 
    finally 
      OmniLock.Release; 
    end; // try/finally 

    AStream   := TIdMultipartFormDataStream.Create; 

    with Stream.AddFile('file_name', FileName, 'application/octet-stream') do 
    begin 
      HeaderCharset := 'utf-8'; 
      HeaderEncoding := '8'; 
    end; // with 

    IdHTTP.Post('https://www.domain.com/post.php', AStream); 
    AStream.Free; 
end; 

但它不起作用!调用AddCookies当()

项目MYEXE.EXE引发的异常类EAccessViolation有消息 “访问冲突在地址00000000读取地址00000000的”我得到这个例外。

我也试过使用assign(),即。

TheCookieManager.CookieCollection.Assign(GlobalCookieManager.CookieCollection); 

但我仍然得到同样的异常,通常是在这里:

TIdCookieManager.GenerateClientCookies() 

任何人知道如何解决这一问题?

+2

在OnNewCookie作业中,你在做什么?当我看到多层指针转换时,缠绕在一个匿名方法上,以'end)^)+ $ 0C)^;'结尾,我有点紧张。 –

+0

我同意这不是最好的代码,但正如我在代码中写的,OnNewCookie在那里保持本地和全球Cookie管理器同步(并且,据我所知,问题不在于OnNewCookie事件) – TheDude

+2

我同意@MasonWheeler。 OnNewCookie事件需要一个对象实例的非静态方法,而不是匿名过程。 'TIdCookieManager'会传递一个隐藏的'Self'指针给事件处理程序,但是你的匿名参数并不能解决这个问题,所以剩下的事件参数会被搞乱。 –

回答

2

回应评论:

谢谢你们,我转换成了一个正常的方法,但我仍然得到AddCookies()中的异常,最后一行发生在行 FRWLock.BeginWrite;在这个程序中 TIdCookies。LockCookieList(AAccessType:TIdCookieAccess): TIdCookieList;

如果您的错误是与Read of address 00000000的访问冲突,这是非常具体的含义。这意味着你正在尝试使用的对象进行操作。

当你得到这个,打破调试器。如果错误发生在您所说的发生的线上,那么几乎可以肯定SelfFRWLock在这一点上。检查两个变量并找出哪一个尚未构建,然后将指向该解决方案。

+0

鉴于已经显示的代码,很可能'GlobalCookieManager'对象在使用之前未被实例化。 –

3

如果我猜的话,我会说你的问题是在这里的某个地方:

// Automatically sync cookies between local & global Cookie managers 
@TheCookieManager.OnNewCookie := pPointer(Cardinal(pPointer(procedure(ASender : TObject; ACookie : TIdCookie; var VAccept : Boolean) 
begin 
     OmniLock.Acquire; 
     try 
     GlobalCookieManager.CookieCollection.AddCookie(ACookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL{IdHTTP.URL}); 
     finally 
       OmniLock.Release; 
     end; // try/finally 

     VAccept := True; 
end)^) + $0C)^; 

我不知道该$0C神奇的数字是有什么,但我敢打赌,所有这些转换非常有因为你有一段时间让编译器接受这个。它给你输入错误,说你不能把一件事分配给另一件事。

这些类型的错误是有原因的!如果你在类型系统中进行破解,事情很可能会破裂。尝试将该匿名方法转换为TUploadThread上的常规方法,并以此方式进行分配,并查看它是否无法更好地工作。

+0

谢谢梅森,看到[我的评论](http://stackoverflow.com/questions/10439511/global-thread-safe-cookies-manager-with-indy#comment13477337_10439511) – TheDude

5

不要对OnNewCookie事件使用匿名过程。使用普通类的方法,而不是:

procedure TUploadThread.NewCookie(ASender: TObject; ACookie : TIdCookie; var VAccept : Boolean); 
var 
    LCookie: TIdCookie; 
begin 
    LCookie := TIdCookieClass(ACookie.ClassType).Create; 
    LCookie.Assign(ACookie); 
    OmniLock.Acquire; 
    try 
    GlobalCookieManager.CookieCollection.AddCookie(LCookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL); 
    finally 
    OmniLock.Release; 
    end; 
    VAccept := True; 
end; 

或者:

procedure TUploadThread.NewCookie(ASender: TObject; ACookie : TIdCookie; var VAccept : Boolean); 
begin 
    OmniLock.Acquire; 
    try 
    GlobalCookieManager.CookieCollection.AddServerCookie(ACookie.ServerCookie, TIdHTTP(TIdCookieManager(ASender).Owner).URL); 
    finally 
    OmniLock.Release; 
    end; 
    VAccept := True; 
end; 

然后使用它是这样的:

procedure TUploadThread.Upload(FileName : String); 
var 
    IdHTTP   : TIdHTTP; 
    TheSSL   : TIdSSLIOHandlerSocketOpenSSL; 
    TheCompressor : TIdCompressorZLib; 
    TheCookieManager : TIdCookieManager; 
    TheStream  : TIdMultipartFormDataStream; 
begin 
    IdHTTP := TIdHTTP.Create(nil); 
    try 
    ... 
    TheCookieManager := TIdCookieManager.Create(IdHTTP); 
    TheCookieManager.OnNewCookie := NewCookie; 

    with IdHTTP do 
    begin 
     HTTPOptions  := [hoForceEncodeParams, hoNoParseMetaHTTPEquiv]; 
     AllowCookies := True; 
     HandleRedirects := True; 
     ProtocolVersion := pv1_1; 

     IOHandler  := TheSSL; 
     Compressor  := TheCompressor; 
     CookieManager := TheCookieManager; 
    end; // with 

    OmniLock.Acquire; 
    try 
     // Load login info/cookies 
     TheCookieManager.CookieCollection.AddCookies(GlobalCookieManager.CookieCollection); 
    finally 
     OmniLock.Release; 
    end; 

    TheStream := TIdMultipartFormDataStream.Create; 
    try 
     with TheStream.AddFile('file_name', FileName, 'application/octet-stream') do 
     begin 
     HeaderCharset := 'utf-8'; 
     HeaderEncoding := '8'; 
     end; 

     IdHTTP.Post('https://www.domain.com/post.php', TheStream); 
    finally 
     TheStream.Free; 
    end; 
    finally 
    IdHTTP.Free; 
    end; 
end; 
+0

谢谢雷米,但我仍然有同样的问题...见[我的评论](http://stackoverflow.com/questions/10439511/global-thread-safe-cookies-manager-with-indy#comment13477337_10439511) – TheDude

+0

看到我的其他意见。 –

+1

事实证明'AddCookie()'接受了传递给它的cookie的所有权。所以你最终会得到多个引用相同物理cookie对象的'TIdCookieManager'对象。这会导致cookies被破坏。我看到两种可能的解决方案:让OnNewCookie直接调用具有“ACookie”副本而不是“ACookie”的'AddCookie()',或者调用'AddServerCookie(ACookie.ServerCookie)'而不是'AddCookie()'。 –