idhttp - get response string while loading page - string

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.

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.

Related

Reading thread's fields from synchronized event handler

Is it safe to read a thread object's fields from an event handler called by the Synchronize procedure?
For example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TMyThread = class(TThread)
public
Max : Integer;
Position : Integer;
OnPositionChanged : TNotifyEvent;
procedure Execute(); override;
end;
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
procedure FormCreate(Sender: TObject);
private
procedure MyOnPositionChanged(Sender : TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
Th : TMyThread;
procedure TMyThread.Execute();
begin
while not Terminated do
begin
//doing stuffs
Sleep(500);
//position + 1
Inc(Position);
//event handler
if(Assigned(OnPositionChanged)) then
begin
Synchronize(
procedure()
begin
OnPositionChanged(Self);
end
);
end;
//check for reaching the max value
if(Position = Max)
then Terminate;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//preparing thread
Th := TMyThread.Create(True);
Th.FreeOnTerminate := True;
Th.Max := ProgressBar1.Max;
Th.Position := ProgressBar1.Position;
Th.OnPositionChanged := MyOnPositionChanged;
//starting thread
Th.Start;
end;
procedure TForm1.MyOnPositionChanged(Sender : TObject);
begin
//updating progressbar
ProgressBar1.Position := (Sender as TMyThread).Position;
end;
end.
I'm wondering if there could be some thread-safety problem in reading the thread's fields from the main thread while the other thread is running
Yes, this is generally safe. The thread's Execute() method is blocked while Synchronize() is running, so the thread won't be updating the fields while the main thread is using them.
Where this can break down is if you happen to have another thread updating the same fields without Synchronize()'ing access to them.

Delphi - random access violation in DLL Thread

I do something wrong, but what exactly?
I have simple DLL with thread. From App I put message in DLL queue, and DLL thread put message to callback.
When I use push in the application not in main thread everything fine.
Also everything is fine if I don't use DLL at all, and use thread with queue just in the application (or use DLL without TThread).
But when I push from main thread - I get random AV (in random time, in a random place)
uTestThread.pas
unit uTestThread;
interface
uses
Winapi.Windows, System.Classes, System.SyncObjs,
System.Generics.Collections;
type
TTestCallback = procedure(Data: Pointer);
TTestThread = class(TThread)
private
FLock: TRTLCriticalSection;
FQueue: TQueue<Pointer>;
FCallback: TTestCallback;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure Push(Data: Pointer);
property Callback: TTestCallback read FCallback write FCallback;
end;
implementation
{ TTestThread }
constructor TTestThread.Create;
begin
InitializeCriticalSection(FLock);
FQueue:=TQueue<Pointer>.Create;
Inherited Create(True);
end;
destructor TTestThread.Destroy;
begin
FQueue.Free;
DeleteCriticalSection(FLock);
inherited;
end;
procedure TTestThread.Execute;
var
O: Pointer;
begin
while not Terminated do
begin
EnterCriticalSection(FLock);
try
if FQueue.Count > 0 then
begin
if Assigned(FCallback) then
begin
O:=FQueue.Dequeue;
FCallback(O);
end;
end;
except //on E: Exception do
//OutputDebugString(PChar('==='+E.Message+'==='));
end;
LeaveCriticalSection(FLock);
end;
end;
procedure TTestThread.Push(Data: Pointer);
begin
EnterCriticalSection(FLock);
try
FQueue.Enqueue(Data);
finally
LeaveCriticalSection(FLock);
end;
end;
end.
DLL
library lib;
uses
uTestThread in 'src\test\uTestThread.pas';
{$R *.res}
var
TT: TTestThread;
procedure Push(Data: TTestDataOut);
begin
TT.Push(Data);
end;
procedure TestThreadCreate(Callback: TTestCallback);
begin
TT:=TTestThread.Create;
TT.Callback:=Callback;
TT.Start;
end;
exports
TestThreadCreate,
Push;
begin
end.
App form
unit frmMain;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uTestThread;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
procedure AddX;
public
{ Public declarations }
end;
var
Form2: TForm2;
procedure TestThreadCreate(Data: TTestCallback); external 'lib.dll';
procedure Push(Data: Pointer); external 'lib.dll';
implementation
{$R *.dfm}
procedure TestCallback(Data: TTestDataOut);
begin
// OutputDebugString(PChar(TStringStream(Data).DataString));
TObject(Data).Free;
end;
{ TForm2 }
procedure TForm2.AddX;
var
I: Integer;
S: string;
O: Pointer;
begin
for I:=0 to 9999 do
begin
// S:='F'+IntToStr(I)+' zzzzzzzzzzzzzzzzzzzzzzzzzWWzzzzzzzzzzzsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssszzzx';
// O:=TStringStream.Create(S);
O:=TObject.Create;
Push(O);
end;
OutputDebugString(PChar('End'));
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
TestThreadCreate(TestCallback);
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
// Everything fine !!!
//
// TThread.CreateAnonymousThread(procedure
// begin
// AddX;
// end).Start;
// Get random AV !!!
AddX;
end;
end.
AV example
---------------------------
GExperts Debugger Exception Notification
---------------------------
Project lib.exe raised exception class EAccessViolation with message 'Access violation at address 004059B1. Read of address FFFFFFFC'.
---------------------------
ThreadId=2644
ProcessId=130
ThreadName=""
ExceptionMessage="Access violation at address 004059B1. Read of address FFFFFFFC"
ExceptionName="EAccessViolation"
ExceptionDisplayName="$C0000005"
ExceptionAddress=004059B1
FileName="GETMEM.INC"
LineNumber=1973
---------------------------
To demonstrate the problem, I use "while True do"
I know that FCallback should also be protected, but for demonstration I skip this
I dont create a thread in DllMain because:
https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
As lock I tried to use TRTLCriticalSection / TCriticalSection / TMonitor / TMutex
As message I tried to use Pointer / WideString / PChar (and copy string)
As callback I tried to use Interface and Pointer to Procedure
As queue I tried TQueue<> / TList<> / TList / Array
I suspect something wrong with callback, because if I use queued item in DLL memory everything also fine

Delphi XE + thread + idHttp without memory leaks

At first HI ALL and sry for my English.
can somebody share work source with create + destroy threads with simple GET in execute ?
i try do it by myself but always get memory leaks((
i test it with code at end of source
initialization
ReportMemoryLeaksOnShutdown := True;
btw ill Google it 2 week and test many samples... and always have leaks by default =(
delphi XE7 32bit at windows 7 x64
when i press stop button i still see some connections
after closing i get this message
cant post image, need 10 reputation...
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, sButton, sMemo, sEdit,
sSpinEdit, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,System.SyncObjs;
type
TForm1 = class(TForm)
StartBtn: TsButton;
StopBtn: TsButton;
ThreadCount: TsSpinEdit;
sdt1: TsEdit;
sm1: TsMemo;
procedure StartBtnClick(Sender: TObject);
procedure StopBtnClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
Thread = class(TThread)
private
HTTP : TIdHTTP;
result:integer;
InputIndex:integer;
public
procedure Local;
constructor Create(CreateSuspended:boolean);
destructor Destroy; override;
protected
procedure Execute; override;
end;
var
Form1: TForm1;
LocalWork: Boolean;
target: string;
implementation
{$R *.dfm}
constructor Thread.Create(CreateSuspended: boolean);
begin
Inherited Create(true);
FreeOnTerminate:=true;
HTTP:=TIdHTTP.Create(nil);
HTTP.ReadTimeout := 2000;
Resume;
end;
destructor Thread.Destroy;
begin
try
If HTTP.Connected then
begin
HTTP.Disconnect(false);
HTTP.IOHandler.InputBuffer.Clear();
HTTP.IOHandler.Close;
Terminate;
end;
finally
WaitFor;
FreeAndNil(HTTP);
end;
inherited;
end;
procedure Thread.Execute;
begin
while (LocalWork=True) do
begin
if LocalWork=true then
begin
HTTP.Get(target);
if HTTP.ResponseCode=200 then
begin
result:=1;
end
else
begin
result:=2;
end;
Synchronize(Local);
end
else
begin
EndThread(0);
end;
end;
EndThread(0);
end;
procedure Thread.Local;
begin
if result=1 then Form1.sm1.Lines.Add('Good ');
if result=2 then Form1.sm1.Lines.Add('Bad ');
end;
procedure TForm1.StartBtnClick(Sender: TObject);
var
i:integer;
begin
target := sdt1.Text;
LocalWork := True;
for I := 0 to ThreadCount.Value-1 do
begin
sm1.Lines.Add('Thread createrd '+inttostr(i));
Thread.Create(true); // создаем замароженный поток
end;
end;
procedure TForm1.StopBtnClick(Sender: TObject);
begin
LocalWork:=false;
end;
initialization
ReportMemoryLeaksOnShutdown := True;
end.
Inside the thread constructor, call inherited Create(false);. And skip the Resume call at the end. The thread will not start until the constructor has finished anyway.
In the thread Execute method, skip the EndThread calls, since the thread will handle this when the Execute method ends.
In the Destroy destructor, do not call Terminate and Waitfor. They do not belong there at all. The thread is told to FreeOnTerminate, and will do so gracefully.

How do I design a "conveyor" of operations with OmniThreadLibrary?

I have a Windows Delphi application with "Start" and "Stop" menu items accessible via a notification icon. After click on "Start", I need to do the following (as I see implementation):
ThreadMonitor: The first thread is waiting for the appearance of the specified file in the specified folder.
ThreadParse: Once the file appears, it should be transferred to another thread (for parsing content) and continue monitoring for the next file.
ThreadDB: Once all data are parsed, save them into MySQL DB. (Another background thread with active DB connection?)
ThreadLog: If there are any errors in the steps 1–3, write them to a log file (another background thread?) without interrupting the steps 1–3.
That is, it turns out that something like a continuous conveyor, whose work is stopped only by pressing Stop.
What should I use from a whole variety of methods of OmniThreadLibrary?
It would probably be best to use Parallel.BackgroundWorker for logging and Parallel.Pipeline for data processing. Here's a sketch of a solution (compiles, but is not fully implemented):
unit PipelineDemo1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
OtlCommon, OtlCollections, OtlParallel;
type
TfrmPipelineDemo = class(TForm)
btnStart: TButton;
btnStop: TButton;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
private
FLogger : IOmniBackgroundWorker;
FPipeline: IOmniPipeline;
strict protected //asynchronous workers
procedure Asy_LogMessage(const workItem: IOmniWorkItem);
procedure Asy_Monitor(const input, output: IOmniBlockingCollection);
procedure Asy_Parser(const input: TOmniValue; var output: TOmniValue);
procedure Asy_SQL(const input, output: IOmniBlockingCollection);
public
end;
var
frmPipelineDemo: TfrmPipelineDemo;
implementation
uses
OtlTask;
{$R *.dfm}
procedure TfrmPipelineDemo.Asy_LogMessage(const workItem: IOmniWorkItem);
begin
//log workItem.Data
end;
procedure TfrmPipelineDemo.Asy_Monitor(const input, output: IOmniBlockingCollection);
begin
while not input.IsCompleted do begin
if FileExists('0.0') then
output.TryAdd('0.0');
Sleep(1000);
end;
end;
procedure TfrmPipelineDemo.Asy_Parser(const input: TOmniValue; var output: TOmniValue);
begin
// output := ParseFile(input)
FLogger.Schedule(FLogger.CreateWorkItem('File processed: ' + input.AsString));
end;
procedure TfrmPipelineDemo.Asy_SQL(const input, output: IOmniBlockingCollection);
var
value: TOmniValue;
begin
//initialize DB connection
for value in input do begin
//store value into database
end;
//close DB connection
end;
procedure TfrmPipelineDemo.btnStartClick(Sender: TObject);
begin
FLogger := Parallel.BackgroundWorker.NumTasks(1).Execute(Asy_LogMessage);
FPipeline := Parallel.Pipeline
.Stage(Asy_Monitor)
.Stage(Asy_Parser)
.Stage(Asy_SQL)
.Run;
end;
procedure TfrmPipelineDemo.btnStopClick(Sender: TObject);
begin
FPipeline.Input.CompleteAdding;
FPipeline := nil;
FLogger.Terminate(INFINITE);
FLogger := nil;
end;
end.

How can I terminate thread directly outside of thread in delphi?

I want to terminate thread by clicking the button. If the thread normally works without user interruption it is OK but sometimes user needs to abort thread and that's the question that how user abort the thread.
Here is my code that I tested:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, mmsystem, ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
procedure Image1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
end;
type
hangth = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
procedure play;
end;
var
Form1: TForm1;
played: boolean;
szalhang: hangth;
implementation
{$R *.dfm}
procedure hangth.play;
begin
played := true;
szalhang.Terminate;
end;
procedure hangth.Execute;
begin
played := false;
SndPlaySound(pchar('hang.wav'), SND_SYNC);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
played := true;
end;
procedure TForm1.Image1Click(Sender: TObject);
begin
if played = true then begin
szalhang := hangth.Create(true);
szalhang.Resume;
end else begin
szalhang.Terminate();
// here i want to terminate thread, but it doesn't want to be killed.
end;
end;
end.
When you call TThread.Terminate(), it sets the TThread.Terminated property to true and does nothing else. It is the responsibility of your TThread.Execute() code to look at the TThread.Terminated property periodically and exit gracefully when it is True. However, in this situation, that is not possible because SndPlaySound() is blocking the thread, and there is no way to interrupt SndPlaySound() when it is running in SND_SYNC mode. Your only option would be to use the Win32 API TerminateThread() function to perform a brute-force termination of the thread.
Since you obviously need more control over the playback of the audio, and detection of when the audio is finished playing, then SndPlaySound() is not the best solution for your needs. You have a TForm, you might consider using Delphi's TMediaPlayer component, for example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.MPlayer;
type
MPlayerState = (mpsClosed, mpsOpened, mpsPlaying);
TForm1 = class(TForm)
Image1: TImage;
MediaPlayer1: TMediaPlayer;
procedure MediaPlayer1Notify(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Image1Click(Sender: TObject);
private
{ Private declarations }
State: MPlayerState;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm56.FormCreate(Sender: TObject);
begin
State := mpsClosed;
MediaPlayer1.FileName := 'C:\full path to\hang.wav';
end;
procedure TForm56.MediaPlayer1Notify(Sender: TObject);
begin
case MediaPlayer1.Mode of
mpStopped, mpPlaying:
State := mpsOpened;
end;
end;
procedure TForm1.Image1Click(Sender: TObject);
begin
if State = mpsClosed then
begin
MediaPlayer1.Notify := False;
MediaPlayer1.Wait := True;
MediaPlayer1.Open;
State := mpsOpened;
end;
if State = mpsOpened then
begin
MediaPlayer1.Notify := True;
MediaPlayer1.Wait := False;
MediaPlayer1.Play;
if MediaPlayer1.Error = 0 then
State := mpsPlaying
end else
begin
MediaPlayer1.Notify := False;
MediaPlayer1.Wait := True;
MediaPlayer1.Stop;
State := mpsOpened;
MediaPlayer1.Notify := False;
MediaPlayer1.Wait := True;
MediaPlayer1.Close;
State := mpsClosed;
end;
end;
end.

Resources