I have an application that listens for a tcp client connection. When the client connects, the client sends over a large chunk of data, in one example 100 k. The Indy TCPServer receives it, processes/reformats the data, sends it on to an http server in the cloud, receives a response, creates an acknowledgement based on the response, and sends it back to the client, disconnecting the connection.
procedure TMyApp.TCPConnect(AContext: TIdContext);
begin
try
AContext.Connection.Socket.ReadTimeout := 10000;
except
on E:Exception do
AContext.Connection.Disconnect;
end;
end;
procedure TMyApp.TCPDisconnect(AContext: TIdContext);
begin
SetLength(FMDMStr,0);
end;
function TMyApp.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
if AnIOHandler.InputBufferIsEmpty then
Exit;
Result := AnIOHandler.WaitFor(#28#13,True,True);
if Pos(#28#13,Result) = 0 then
Result := Result #28#13;
end;
procedure TMyApp.WriteTCPResponse(AnIOHandler: TIdIOHandler);
var
bytes: TIdBytes;
begin
BuildACK(FMDMStr,bytes);
AnIOHandler.WriteDirect(bytes, Length(bytes));
end;
procedure TMyApp.TCPExecute(AContext: TIdContext);
begin
try
FMDMStr := TransferTCPBytesToString(AContext.Connection.IOHandler);
// test string for HL/7 terminating characters
if Pos(#28#13, FMDMStr) > 0 then
begin
FormatAndSendHTTPMsg(FMDMStr);
// send the response back to tcp client
WriteTCPResponse(AContext.Connection.IOHandler);
end;
except
on E:Exception do
AContext.Connection.Disconnect;
end;
end;
I have used this app in varying scenarios for over 2 years and it works well. I've tested it across multiple windows 10 & windows server 2016 SE servers and it works fine.
The most recent deployment isn't working at all; the same client sends across >100k of data, connecting to the TIdTcpServer; the execute method fires, but there is a -0- sized input buffer. I've set the receive buffer to 256k just to be on the safe side. Here's what I'm logging:
AContext.Connection.IOHandler.InputBufferAsString
AContext.Connection.IOHandler.RecvBufferSize
AContext.Connection.IOHandler.InputBufferIsEmpty
ReceiveBuffer logs as 262144. InputBufferIsEmpty always comes back true! And of course the InputBufferAsString := ''.
So I'm wondering if this could possibly be a security "feature" on the server/network/domain that the app is running on? I've tested it on two different servers on the domain, exactly the same result. Client connects, logs that it is sending, the IdTcpServer logs the connection, but receives nothing.
Any thoughts or advice will be greatly appreciated! TIA
CodePudding user response:
The try..except
in TCPConnect()
is useless. Setting the ReadTimeout
is not going to raise an exception. And in any case, if an uncaught exception escapes the OnConnect
or OnException
event, the server will disconnect the client automatically.
FMDMStr
is not being used in a thread-safe manner. Each TIdContext
runs in its own thread. DO NOT share variables across thread boundaries without proper synchronization. In this case, if there is only ever 1 client connected (and you enforce that by setting TIdTCPServer.MaxConnections=1
), then so be it. Otherwise, FMDMStr
should not be a class member variable at all, it should be a local variable of TCPExecute()
instead, so that each connected client operates on its own string
.
The InputBufferIsEmpty
check in TransferTCPBytesToString()
needs to be removed. Let WaitFor()
block until data arrives or timed out. Also, since you are setting AInclusive=True
, the return value of WaitFor()
will always include #28#13
at the end, so the Pos()
check is useless and should be removed. Also, since TransferTCPBytesToString()
always returns a string with #28#13
on the end, the Pos()
check in TCPExecute()
is useless, too.
In WriteTCPResponse()
, you SHOULD NOT be using TIdIOHandler.WriteDirect()
at all. Use appropriate TIdIOHandler.Write()
overloads instead. There is an overload for sending TIdBytes
.
With that said, try something more like this:
procedure TMyApp.TCPConnect(AContext: TIdContext);
begin
AContext.Connection.IOHandler.ReadTimeout := 10000;
end;
function TMyApp.TransferTCPBytesToString(AnIOHandler: TIdIOHandler): String;
begin
// will throw an exception on timeout...
Result := AnIOHandler.WaitFor(#28#13,True,True);
end;
procedure TMyApp.WriteTCPResponse(AnIOHandler: TIdIOHandler; const AMsg: string);
var
bytes: TIdBytes;
begin
BuildACK(AMsg, bytes);
AnIOHandler.Write(bytes);
end;
procedure TMyApp.TCPExecute(AContext: TIdContext);
var
FMDMStr: string;
begin
FMDMStr := TransferTCPBytesToString(AContext.Connection.IOHandler);
// TODO: change FormatAndSendHTTPMsg() to return the response string
// directly, rather than save it in a class member variable...
MDMStr := FormatAndSendHTTPMsg(FMDMStr);
// send the response back to tcp client
WriteTCPResponse(AContext.Connection.IOHandler, FMDMStr);
end;
FYI, on a side note, your code mentions HL/7. Indy has a TIdHL7
component that can operate in server mode, running an internal TIdTCPServer
that reads in and responds to HL/7 messages, firing an OnReceiveMessage
event for each message. In your scenario, you could set TIdHL7.Port
to your desired listening port, set TIdHL7.CommunicationMode=cmSynchronous
, set TIdHL7.isListener=True
, assign a handler to TIdHL7.OnReceiveMessage
to send your HTTP message, and then call TIdHL7.Start()
at runtime when ready.
Just something to consider...
CodePudding user response:
First, the default Windows Indy socket buffer is 32 KB big in size. So, when You would like to send all in one (what I do not prefered) make Your sending packets smaller. So, You can quicker check Your connection for Time-Out's or other disadvantages, too. BTW Question: what Version of Indy do You use - Version 9 or 10 ?
Second, check Your server/client Codes where You read/write the "request", and "responses" exists - A read function should end with \r\n when You in text mode.
Third, check, if You have non-blocking or blocking server Code. This means - You should install a TThread for each Connection to handle all Connection's properly (may be with other properties). So, the Connections do not overlap existing previous Connections.
Fourth (for advanced developers): You should be consider, to have a "second" Connection to each one - this can help to hold the line (hops...
Fiveth: (for advanced develppers): You should be in mind with security aspects. This means, You should using SSL certificates for Your Connections (Indy provides simple click, and edit components for this. You task is it, to create a "self signed certificate" (PuttyGen (on Windows) or on Linux - see google for details) - if You own Your public certificate, You don't need this step - have a look to "Lets Encrypt" (a public free SSL authority, but be warned: You have to renew develper SSL certificates each third month (a certbot script helps You to do all these things).
If You have question's, feel free to ask, I will try to help You.
Happy X-mas days