after a stream was passed to CommandGet, when can it be freed? - delphi

with indy TIdHTTPServer, on the even of OnCommandGet, there is a possiblity to pass to AResponseInfo.ContentStream a stream with the data.
which is fine.
when can i release that stream?
assuming the server can get multi requests, and any request could be handled at given time, and one stream can finish arbitrary to the other.
where could the stream be freed?
code example:
var
StreamsToFree : TList;
//assume StreamsToFree := TList.create; properly
procedure TObject.IdHttpServerCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
stream : TFileStream;
begin
stream := TFileSTream.create('file.name');
AResponseInfo.ContentStream := stream;
AResponseInfo.ResponseNo := 200;
StreamsToFree.Add(generateReceiptXML);
end;
When can the stream be freed? on what even, and how do we know the IdHttpServer , finished its transfer?

When you assign it to the ContentStream property, Indy becomes the owner of your stream and will free it when it's no longer needed.
Edit: Assuming you leave FreeContentStream property set to True (which is the default).

Related

TIdHTTPServer + TIdServerIOHandlerSSLOpenSSL = unstable work

I placed TIdHTTPServer and TIdServerIOHandlerSSLOpenSSL on the form, set sertificate file path for the TIdServerIOHandlerSSLOpenSSL
In the TIdHTTPServer onCommandGet event I send answer in this way:
FullResponce:=RespHeaders+#13#10+RespData;
//dump for debug
FileHandle := CreateFileW('c:\lastresp.txt', GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if FileHandle <> INVALID_HANDLE_VALUE then
begin
try
WriteFile(FileHandle, FullResponce[1], length(FullResponce), LongWord(ReadRes), nil);
finally
CloseHandle(FileHandle);
end;
end;
//response
AContext.Connection.IOHandler.WriteDirect(#FullResponce[1],length(FullResponce),0);
exit;
When I'm trying to open address '127.0.0.1:433' in the Internet Explorer, sometimes it opens page, sometimes says "unable to display page"
There is no any pattern or logic in this, I just refresh the page in the explorer 10 times, and for example, attempts 1,2,4,6,7,9,10 had open the page, and the rest ended with an error
Dupm of the answer in the lastresp.txt is always correct and always the same
When the error occurs, IE in the network console shows that he received 0 bytes
Any ideas how to fix this?
Bugs in Indy?
Update
Solved, problem was not in code but in sniffer app used to control traffic(
How you have implemented the OnCommandGet event is not the correct way to send a response to the client.
First off, you are not supposed to use TIdIOHandler.WriteDirect() directly at all. But if you do, you must pass it a TIdBytes, ie a dynamic array of bytes, not a string.
But more importantly, to send an HTTP response to the client, the correct way is to populate the AResponseInfo parameter and let TIdHTTPServer handle the actual socket writing for you.
For example:
procedure TMyForm.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
...
AResponseInfo.ResponseCode := 200;
AResponseInfo.ContentText := RespData;
// set other AResponseInfo properties as needed, like ContentType, etc...
end;
When the OnCommandGet handler exits, TIdHTTPServer will send the contents of the AResponseInfo to the client, if it has not already been sent.
Also, your use of WriteFile() is incorrect if you are using Delphi 2009+ and FullResponce is a normal string (ie, a UnicodeString). It would need to look like this instead:
WriteFile(FileHandle, PChar(FullResponce)^, Length(FullResponce) * SizeOf(Char), LongWord(ReadRes), nil);
Consider using TFile.WriteAllText() instead:
TFile.WriteAllText('c:\lastresp.txt', FullResponce);
However, another way to add logging to your TIdHTTPServer code is to instead assign a TIdLogFile component to the AContext.Connection.IOHandler.Intercept property and let Indy itself log exactly what it sends to the client, eg:
uses
..., IdLogFile;
procedure TMyForm.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Log: TIdLogFile;
begin
Log := TIdLogFile.Create(nil);
try
Log.Filename := 'c:\lastresp.txt';
Log.LogTime := False;
Log.ReplaceCRLF := False;
Log.Active := True;
AContext.Connection.IOHandler.Intercept := Log;
...
AResponseInfo.ResponseCode := 200;
AResponseInfo.ContentText := RespData;
// set other AResponse properties as needed, like ContentType, etc...
...
AResponse.WriteContent;
finally
Log.Free;
end;
end;

What could be causing IdTCPServer to fail before reading all the data OnExecute event?

This code work's fine when I send data across the LAN with an Indy client component, but when I receive data from an external application from the web, it's causing it to fail. Could there be something on the client-side that is causing IdTCPServer to disconnect before all the data is read? An average of 33,000 characters are being sent by the client. Any suggestions?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
strm: TMemoryStream;
RxBuf: TIdBytes;
begin
Memo1.Clear;
strm := TMemoryStream.Create;
try
// read until disconnected
AContext.Connection.IOHandler.ReadStream(strm, -1, true);
strm.Position := 0;
ReadTIdBytesFromStream(strm, RxBuf, strm.Size);
finally
strm.Free;
end;
Memo1.Lines.Add(BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
end;
I also tryed this other code, in this case unlike the first code it only reads part of the data beeing sent. Is there a way to make the IdTCPServer Handler wait until all the data is collected?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
RxBuf: TIdBytes;
begin
RxBuf := nil;
with AContext.Connection.IOHandler do
begin
CheckForDataOnSource(10);
if not InputBufferIsEmpty then
begin
InputBuffer.ExtractToBytes(RxBuf);
end;
end;
AContext.Connection.IOHandler.WriteLn('000');
Memo1.Lines.Add( BytesToString(RxBuf) );
end;
This code you posted as an answer is all wrong.
First off, you can't use BytesToString() on arbitrary byte blocks, that won't handle multi-byte encodings like UTF-8 correctly.
Also, you are not looking for the EOT terminator correctly. There is no guarantee that it will be the last byte of RxBuf after each read, if the client sends multiple XML messages. And even if it were, using Copy(BytesToString(), ...) to extract it into a string will never result in a blank string, like your code is expecting.
If the client sends an EOT terminator at the end of the XML, there is no need for a manual reading loop. Simply call TIdIOHandler.ReadLn() with the EOT terminator, and let it handle the read looping internally until the EOT is reached.
Also, the CoInitialize() and CoUninitialize() calls should be done in the OnConnect and OnDisconnect events, respectively (actually, they would be better called in a TIdThreadWithTask descendant assigned to the TIdSchedulerOfThread.ThreadClass property, but that is a more advanced topic for another time).
Try something more like this:
procedure TFrmMain.IdTCPServer1Connect(AContext: TIdContext);
begin
CoInitialize(nil);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TFrmMain.IdTCPServer1Disconnect(AContext: TIdContext);
begin
CoUninitialize();
end;
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
XML: string;
begin
cdsSurescripts.Close;
XML := AContext.Connection.IOHandler.ReadLn(#4);
Display('CLIENT', XML);
AContext.Connection.IOHandler.WriteLn('000');
end;
Personally, I would take a different approach. I would suggest using an XML parser that supports a push model. Then you can read arbitrary blocks of bytes from the connection and push them into the parser, letting it fire events to you for completed XML elements, until the terminator is reached. This way, you don't have to waste time and memory buffering the entire XML in memory before you can then process it.
For further reference to anyone, I had to create a loop and wait for an EOT chr(4) send by the client in order to collect all the data on the IdTCPServer1Execute. This happens because the data is fragmented by Indy, The code looks something like this:
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
Len: Integer;
Loop: Boolean;
begin
CoInitialize(nil);
cdsSurescripts.Close;
Loop := True;
while Loop = true do
begin
if AContext.Connection.IOHandler.Readable then
begin
AContext.Connection.IOHandler.ReadBytes( RxBuf,-1, True);
Len := Length(BytesToString(RxBuf));
if Copy(BytesToString(RxBuf), Len, 1) = '' then
begin
loop := False;
end;
end;
end;
Display('CLIENT', BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
CoUninitialize();
end;

IdHTTPServer and IdHTTP with Encoding UTF8

I am testing a localhost server using TIdHTTPServer and TIdHTTP. I am having problems with encoding UTF8 data.
client side:
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
res: string;
begin
res:=IdHTTP1.Get('http://localhost/?msg=đi chơi thôi');
Memo1.Lines.Add(res);
end;
Server side:
procedure TForm1.OnCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
Memo1.Lines.Add(ARequestInfo.Params.Values['msg']); // ?i ch?i th?i
AResponseInfo.CharSet := 'utf-8';
AResponseInfo.ContentText := 'chào các bạn'; // chào các b?n
end;
I want to send đi chơi thôi and receive chào các bạn. But the server receives ?i ch?i th?i and the client receives chào các b?n.
Can anyone help me?
TIdHTTP transmits the URL exactly as you give it, but http://localhost/?msg=đi chơi thôi is not a valid URL that can be transmitted as-is, as URLs can only contain ASCII characters. Unreserved ASCII characters can be used as-is, but reserved and non-ASCII characters MUST be charset-encoded into bytes and then those bytes must be url-encoded in %HH format, eg:
IdHTTP1.Get('http://localhost/?msg=%C4%91i%20ch%C6%A1i%20th%C3%B4i');
You must ensure you pass only valid url-encoded URLs to TIdHTTP.
In this example, the URL is hard-coded, but if you need something more dynamic then use the TIdURI class, eg:
IdHTTP1.Get('http://localhost/?msg=' + TIdURI.ParamsEncode('đi chơi thôi'));
TIdHTTPServer will then decode the parameter data as you are expecting. Both TIdURI and TIdHTTPServer use UTF-8 by default.
When sending a response, you are only setting a CharSet, but you are not setting a ContentType. So TIdHTTPServer will set the ContentType to 'text/html; charset=ISO-8859-1', overwriting your CharSet. You need to explicitly set the ContentType yourself so you can specify a custom CharSet, eg:
AResponseInfo.ContentType := 'text/plain';
AResponseInfo.CharSet := 'utf-8';
AResponseInfo.ContentText := 'chào các bạn';
Or:
AResponseInfo.ContentType := 'text/plain; charset=utf-8';
AResponseInfo.ContentText := 'chào các bạn';
On a side note, TIdHTTPServer is a multi-threaded component. The OnCommand... events are fired in the context of a worker thread, not the main UI thread. So accessing Memo1 directly like you are is not thread-safe. You MUST synchronize with the main UI thread in order to access UI controls safely, eg:
procedure TForm1.OnCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
msg: string;
begin
msg := ARequestInfo.Params.Values['msg'];
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add(msg);
end
);
...
end;

INDY10 Stream write error while I trying to send data trought FTP

I want to put data from Memo1 directly to my FTP server, I've got code:
procedure TForm5.SendClick(Sender: TObject);
var K: TStream;
begin
K := TStream.Create;
Memo1.Lines.SaveToStream(K);
FTP.Host := 'localhost';
FTP.Username := 'login';
FTP.Password := 'haslo';
FTP.Connect;
if FTP.Connected then FTP.Put(K,'');
end;
But when I click "Send" button I've got two errors:
when Memo is empty
when I try send data
TStream is an abstract class. You must never instantiate it. Use a concrete class instead like, for instance, TMemoryStream.
You'll also want to destroy the stream when you are finished with it, or it will leak. Do yourself a favour and set ReportMemoryLeaksOnShutdown to True, for instance in your .dpr file. That will allow you to get a report of all the memory you are leaking when your program terminates.
Your code might run like this:
var
Stream: TMemoryStream;
....
Stream := TMemoryStream.Create;
try
// .... initialize the Indy object
if FTP.Connected then begin
// .... populate stream
Stream.Position := 0;
FTP.Put(Stream, '');
end;
finally
Stream.Free;
end;

How to handle TIdHTTPServer with TIdMultiPartFormDataStream

Hi i need help on how to retrieved the parameters and data using IdHttpServer from indy.
many of my application uses TIdMultiPartFormDataStream to send data over the php. I would like to use the TIdHTTPServer to verify parameters for some reason and forward the request to its destination.
i created a short example for you to see.
uses
IdContext, IdMultipartFormData;
// Server Side------------------------------------------------
IdHTTPServer1.Defaultport := 88;
IdHTTPServer1.active := True;
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
// the request will be pass through its destination by POST/GET
// and send the result back to the client apps.
AResponseInfo.ContentText := ARequestInfo.Params.Text;
end;
// Client Side------------------------------------------------
// This will work using the standard Post or Get
procedure TForm1.btnPost1Click(Sender: TObject);
var
sl: TStringList;
res: String;
begin
sl := TStringList.Create;
try
sl.Add('Param1=Data1');
sl.Add('Param2=Data1');
sl.Add('Param3=Data2');
sl.Add('Param4=Data3');
res := IdHTTP1.Post('http://localhost:88/some.php', sl);
ShowMessage(res);
finally
sl.Free;
end;
end;
//how can i get the parameters and value for this code in my IdHttpServer
procedure TForm1.btnPost2Click(Sender: TObject);
var
mfd: TIdMultiPartFormDataStream;
res: String;
begin
mfd := TIdMultiPartFormDataStream.Create;
try
mfd.AddFormField('Param1', 'Data1');
mfd.AddFormField('Param2', 'Data1');
mfd.AddFormField('Param3', 'Data2');
mfd.AddFormField('Param4', 'Data3');
res := IdHTTP1.Post('http://localhost:88/some.php', mfd);
ShowMessage(res);
finally
mfd.Free;
end;
end;
and how would i know if the Client apps pass a TIdMultiPartFormDataStream type of parameter?
This has been asked and answered many times before in the Embarcadero and Indy forums. Please search through their archives, as well as other archives, like Google Groups, to find code examples.
In a nutshell, when the TIdHTTPServer.OnCommandGet event is triggered, if the AResponseInfo.ContentType property says multipart/form-data (the version of TIdHTTP.Post() you are using will send application/x-www-form-urlencoded instead), the AResponseInfo.PostStream property will contain the raw MIME data that the client posted. You can use the TIdMessageDecoderMIME class to parse it. However, that class was never intended to be used server-side, so it is not very intuitive to use, but it is possible nontheless.
In Indy 11, I am planning on implementing native multipart/form-data parsing directly into TIdHTTPServer itself, but there is no ETA on that yet as we have not started work on Indy 11 yet.

Resources