【问题标题】:Global, thread safe, cookies manager with IndyIndy 的全局、线程安全、cookie 管理器
【发布时间】:2012-05-13 10:45:14
【问题描述】:

我的 Delphi 2010 应用程序使用多线程上传内容,上传的数据被 POST 到需要登录的 PHP/Web 应用程序,所以我需要使用共享/全局 cookie 管理器(我正在使用 Indy10 Revision 4743 ) 因为 TIdCookieManager 不是线程安全的:(

此外,服务器端会话 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() 时出现此异常

Project MyEXE.exe 引发异常类 EAccessViolation 并显示消息 '地址 00000000 的访问冲突。读取地址 00000000'。

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

 TheCookieManager.CookieCollection.Assign(GlobalCookieManager.CookieCollection);

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

 TIdCookieManager.GenerateClientCookies()

有人知道如何解决这个问题吗?

【问题讨论】:

  • 你到底在用 OnNewCookie 分配做什么?当我看到多层指针转换,包裹在一个匿名方法上,以end )^ ) + $0C)^; 之类的东西结尾时,我有点紧张。
  • 我同意这不是最好的代码,但正如我在代码中所写的那样,OnNewCookie 可以使本地和全局 Cookie 管理器保持同步(据我所知,问题是不与 OnNewCookie 事件)
  • 我同意@MasonWheeler。 OnNewCookie 事件需要对象实例的非静态方法,而不是匿名过程。 TIdCookieManager 将向事件处理程序传递一个隐藏的 Self 指针,但您的匿名参数没有考虑到这一点,因此其余的事件参数将被搞砸。
  • 谢谢大家,我转换为普通方法,但在 AddCookies() 中仍然出现异常,最后一个发生在此过程中 FRWLock.BeginWrite;TIdCookies.LockCookieList(AAccessType: TIdCookieAccess): TIdCookieList; 的行中跨度>
  • 您在哪里实例化和释放GlobalCookieManager 对象?由于它在全球范围内使用,因此您应该在单元的 initializationfinalization 块中这样做。

标签: multithreading delphi delphi-2010 indy


【解决方案1】:

不要对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; 

【讨论】:

  • 谢谢 Remy,但我仍然有同样的问题...请参阅 my comment here
  • 原来AddCookie() 拥有传递给它的cookie 的所有权。因此,您最终会得到多个引用相同物理 cookie 对象的 TIdCookieManager 对象。这会导致 cookie 被丢弃。我看到了两种可能的解决方案:让OnNewCookie 调用AddCookie() 并直接使用ACookie 而不是ACookie,或者调用AddServerCookie(ACookie.ServerCookie) 而不是AddCookie()
【解决方案2】:

如果我不得不猜测,我会说你的问题出在某个地方:

 // 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 上的普通方法并以这种方式分配,看看是否效果更好。

【讨论】:

【解决方案3】:

回复评论:

谢谢大家,我转换为正常方法,但我仍然得到 AddCookies() 中的异常,最后一个发生在读取的行中 FRWLock.BeginWrite;在这个过程中 TIdCookies.LockCookieList(AAccessType:TIdCookieAccess): TIdCookieList;

如果您的错误是Read of address 00000000 的访问冲突,则具有非常具体的含义。这意味着您正在尝试对 nil 的对象执行某些操作。

当你得到它时,中断调试器。如果错误发生在您所说的行上,那么几乎可以肯定SelfFRWLock 在这一点上是nil。检查这两个变量并找出尚未构建的变量,这将为您指明解决方案。

【讨论】:

  • 鉴于已显示的代码,很可能GlobalCookieManager 对象在使用之前尚未实例化。
猜你喜欢
  • 2012-02-12
  • 1970-01-01
  • 2018-09-08
  • 1970-01-01
  • 1970-01-01
  • 2016-05-05
  • 1970-01-01
  • 1970-01-01
  • 2018-01-01
相关资源
最近更新 更多