Home > Back-end >  Indy TIdTcpServer input buffer empty
Indy TIdTcpServer input buffer empty

Time:12-16

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

  • Related