2012-05-03 57 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()'。 –