Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit speed of retrieved files with IndyFtpServer (v10)

I'm developing a FTP Server with Delphi XE 6 and Indy10. The problem is that i need to limit the speed of download (must be configurable Ex. 1 KB/s, 1 MB/s, etc.) and i don't make it work. I know some props like BitsPerSec, etc. but this only affect the protocol data exchange, not the file exchange with RETR command. I see in IdFTPServer.pass, and the Stream is converted to string and send with a repeat/until loop (with IOHandler.Write()) but i need some form of control the upload/download process and be able to limit the speed for all incoming client connections. Some help to achieve this please?.

PD: Sorry for my poor english.

I try to implement a CommandHandler for the RETR command with this code:

procedure TForm1.IdFTPServer1CommandHandlers0Command(ASender: TIdCommand);
var
  LContext : TIdFTPServerContext;
  vStream: TFileStream;
  path: String;
  vX: Integer;
  LEncoding: IIdTextEncoding;
begin

  LEncoding := IndyTextEncoding_8Bit;
  LContext := ASender.Context as TIdFTPServerContext;
  path := 'D:\indy_in_depth.pdf';


  try
    vStream := TFileStream.Create(path, fmOpenRead);
    //LContext.DataChannel.FtpOperation := ftpRetr;
    //LContext.DataChannel.Data := VStream;
    LContext.DataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
    LContext.DataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);

    ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
    ASender.SendReply;

    Memo1.Lines.Add('Sending a file with: ' + IntToStr(vStream.Size) + ' bytes');
    //Make control of speed here !
    for vX := 1 to vStream.Size do
      begin
        vStream.Position := vX;
        LContext.Connection.IOHandler.Write(vStream, 1, False);
        if vX mod 10000 = 0 then
          Memo1.Lines.Add('Sended byte: ' + IntToStr(vX));
      end;

    //LContext.DataChannel.InitOperation(False);

    //LContext.Connection.IOHandler.Write(vStream);

    //LContext.KillDataChannel;
    LContext.Connection.Socket.Close;

    VStream.Free;
  except
    on E: Exception do
    begin
      ASender.Reply.SetReply(550, E.Message);
    end;
  end;

end;

But the Filezilla FTP Client show stream data like part of other command.

Filezilla Client

like image 347
Wo_0NDeR ᵀᴹ Avatar asked Oct 31 '17 04:10

Wo_0NDeR ᵀᴹ


1 Answers

The problem is that i need to limit the speed of download (must be configurable Ex. 1 KB/s, 1 MB/s, etc.) and i don't make it work. I know some props like BitsPerSec, etc. but this only affect the protocol data exchange, not the file exchange with RETR command.

BitsPerSec is a property of the TIdInterceptThrottler class (along with RecvBitsPerSec and SendBitsPerSec properties). An instance of that class can be assigned to any TIdTCPConnection object via its Intercept property. This is not limited to just the command connection, it can be used for transfer connections, too.

You can access the TIdTCPConnection object of a file transfer in TIdFTPServer via the TIdFTPServerContext.DataChannel.FDataChannel member, such as in the OnRetrieveFile event:

type
  // the TIdDataChannel.FDataChannel member is 'protected',
  // so use an accessor class to reach it...
  TIdDataChannelAccess = class(TIdDataChannel)
  end;

procedure TForm1.IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
  const AFileName: TIdFTPFileName; var VStream: TStream);
var
  Conn: TIdTCPConnection;
begin
  Conn := TIdDataChannelAccess(ASender.DataChannel).FDataChannel;
  if Conn.Intercept = nil then
    Conn.Intercept := TIdInterceptThrottler.Create(Conn);
  TIdInterceptThrottler(Conn.Intercept).BitsPerSec := ...;
  VStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
end;

An alternative is to create your own T(File)Stream derived class to override the virtual Read() and Write() methods to perform your own throttling as needed, and then assign an instance of that class to the VStream parameter of the OnRetrieveFile and OnStoreFile events.

I see in IdFTPServer.pas, and the Stream is converted to string and send with a repeat/until loop (with IOHandler.Write())

No, a stream is not converted to a string. You are misreading the code. A stream's content is sent as-is as binary data in a single call to the IOHandler.Write(TStream) method.

but i need some form of control the upload/download process and be able to limit the speed for all incoming client connections.

See above.

I try to implement a CommandHandler for the RETR command

You should NOT be handling the RETR command directly. TIdFTPServer already does that for you. You are meant to use the OnRetrieveFile event instead to prepare downloads, and the OnStoreFile event for uploads.

like image 105
Remy Lebeau Avatar answered Nov 17 '22 12:11

Remy Lebeau