0

I need to get received string before page is loaded (to use with asterix http AMI events). So i am trying to access received string in OnWork event of idHttp, but I am getting error:

var
  Form2: TForm2;
  s:TStringStream;

procedure TForm2.Button1Click(Sender: TObject);
begin
  s:=TStringStream.Create;
  idhttp1.Get('http://website.com:8088/asterisk/rawman?action=waitevent&timeout=10',s);
  showmessage(s.DataString); //NO ERROR
end;

procedure TForm2.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
begin
      showmessage(s.DataString); //ERROR HERE
end;

UPDATE: I created custom class (TAMIStringStream) as Remy Lebeau adviced, but still getting an error. What am I doiung wrong?

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdHTTP, cxGraphics, cxControls, cxLookAndFeels,
  cxLookAndFeelPainters, cxContainer, cxEdit, Vcl.StdCtrls, cxTextEdit, cxMemo,
  cxCheckBox;

type
  TAMIStringStream = class(TStringStream)
    FEncoding: TEncoding;
    public
    ReceivedSTR:string;

    function Write(const Buffer; Count: Longint): Longint; override;
  end;

  TForm2 = class(TForm)
    IdHTTP1: TIdHTTP;
    Button1: TButton;
    cxCheckBox1: TcxCheckBox;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;
  s:TAMIStringStream;

implementation

{$R *.dfm}

function TAMIStringStream.Write(const Buffer; Count: Longint): Longint;
var t:string;
begin
  Inherited;

  t := FEncoding.GetString(Bytes, Position - Count, Count);
  form2.memo1.lines.add(t);
  ReceivedSTR := ReceivedSTR + t;
end;


procedure TForm2.Button1Click(Sender: TObject);
begin
  idhttp1.Get('http://website.com:8088/asterisk/rawman?action=login&username=1cami&secret=111');
  s:=TAMIStringStream.Create;
  while cxCheckBox1.Checked do begin
    idhttp1.Get('http://website.com:8088/asterisk/rawman?action=waitevent&timeout=10',s);
  end;
end;

end.

1 Answers1

3

To get at the server's HTTP response data while it is still being downloaded by TIdHTTP, you need to write your own TStream-derived class that overrides the virtual TStream.Write() method, and then you can pass an instance of that class to the AResponseContent parameter of TIdHTTP.Get(). Your Write() method can process data as it is being "written" to your stream (be prepared to process that data in arbitrary chunks, since it is streaming live).

Otherwise, you would have to skip TIdHTTP altogether and use TIdTCPClient instead, implementing the HTTP protocol manually so that you are in full control over reading and writing.

The "AMI over HTTP" protocol documentation (see this and this) shows how to send HTTP requests to AMI and how to poll for events (yes, you have to poll for events when using HTTP). Since the polling does not return until an event is delivered, there is not much reason to read the server's response data in-flight. TIdHTTP.Get() will block until the event is received, then you can process it as needed. So, your first approach should have been fine without a custom stream:

procedure TForm2.Button1Click(Sender: TObject);
var
  s: TStringStream;
begin
  idhttp1.Get('http://website.com:8088/asterisk/rawman?action=login&username=1cami&secret=111');
  s := TStringStream.Create;
  try
    while cxCheckBox1.Checked do
    begin
      IdHttp1.Get('http://website.com:8088/asterisk/rawman?action=waitevent&timeout=10', s);
      Memo1.Lines.Add(s.DataString);
      s.Clear;
    end;
  finally
    s.Free;
  end;
end;

Alternatively:

procedure TForm2.Button1Click(Sender: TObject);
var
  s: String;
begin
  idhttp1.Get('http://website.com:8088/asterisk/rawman?action=login&username=1cami&secret=111');
  while cxCheckBox1.Checked do
  begin
    s := IdHttp1.Get('http://website.com:8088/asterisk/rawman?action=waitevent&timeout=10');
    Memo1.Lines.Add(s);
  end;
end;

Because of TIdHTTP's blocking nature, I would suggest moving the polling into a worker thread:

procedure TMyThread.Execute;
var
  http: TIdHTTP;
  s: String;
begin
  http := TIdHTTP.Create(nil);
  try
    http.Get('http://website.com:8088/asterisk/rawman?action=login&username=1cami&secret=111');
    while not Terminated do
    begin
      s := http.Get('http://website.com:8088/asterisk/rawman?action=waitevent&timeout=10');
      // do something...
    end;
  finally
    http.Free;
  end;
end;

If HTTP polling does not suit your needs, you should consider using "AMI over TCP" instead (see this and this), and use TIdTCPClient for that. You can use a timer or a thread to check for incoming data.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks. I created custom class (see my update above), but still getting error.. What did I do wrong? – user1596704 Aug 06 '15 at 11:17
  • You did not say what error you are getting. However, your `Write()` method is accessing an `FEncoding` object that you have not initialized. Also, `Write()` receives arbitrary chunks of data, so you should not be blindly converting them as-is to a charset-decoded string anyway. Imagine what would happen if the event data contained a Unicode character whose bytes straddle multiple `Write()` calls. It would not decode correctly. – Remy Lebeau Aug 06 '15 at 14:38