1

To listening of events from a Rest server I need a long running HTTP request.

How we do that with Delphi?

My current solution works but the stop of the request from the caller side is not solved properly. I have to stop the thread by using Thread.Terminate. This thread kill leads to unwanted memory leaks. That is why I'm looking for another solution.

The request must be interuptable by the caller also in case where no new data are received from the server. I tried als asynchronous calls with BeginExecute/BeginGet but I saw the same problem that the request can not be stopped at any time without data from the server.

Here is my code which sould work at the end on different plattforms (Win/OSX/ Android):

type
  TMyRestAPI = class
  private
    fClient: THTTPClient;
    fStream: TMemoryStream;
    fReadPos: Int64;
    fOnReceiveData: procedure(const data: string) of object;
    procedure OnRecData(const Sender: TObject; AContentLength: Int64;
      AReadCount: Int64; var Abort: Boolean);
  public
    procedure StartRequest(const URL: string);
    procedure StopRequest;
  end;

procedure TMyRestAPI.StartRequest(const URL: string);
begin
  fClient := THTTPClient.Create;
  fClient.HandleRedirects := true;
  fClient.Accept := 'text/event-stream';
  fClient.OnReceiveData := OnRecData;
  fStream := TMemoryStream.Create;
  fReadPos := 0;
  fThread := TThread.CreateAnonymousThread(
    procedure
    begin
      fClient.Get(URL, fStream);
    end);
  fThread.Start;
end;

procedure TMyRestAPI.OnRecData(const Sender: TObject; AContentLength: Int64;
  AReadCount: Int64; var Abort: Boolean);
var
  ss: TStringStream;
  resp: string;
begin
  ss := TStringStream.Create;
  try
    fStream.Position := readPos;
    ss.CopyFrom(fStream, AReadCount - fReadPos);
    fOnReceiveData(ss.DataString);
    fReadPos := AReadCount;
  finally
    ss.Free;
  end;
end;

procedure TMyRestAPI.StopRequest;
begin
  fThread.Terminate;
  fClient.Free;
  fStream.Free;
end;
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • 3
    Hint: Creating a thread by `TThread.CreateAnonymousThread(..)` creates a `TThread` with `FreeOnTerminate = True`. However, you are accessing `fThread` in `StopRequest`. This cannot end well. – Günther the Beautiful Jun 28 '18 at 11:33
  • @GünthertheBeautiful you can manually set `FreeOnTerminate=False` after creating the thread, though. – Remy Lebeau Jun 28 '18 at 15:53
  • @Schneider The only way to abort an anonymous thread is to exit the anonymous procedure, which means making `Get()` exit. Instead of using a `TMemoryStream`, I would write a custom `TStream` descendant that overrides `Read()` and `Write()` to raise an exception when the HTTP processing should stop. – Remy Lebeau Jun 28 '18 at 15:53
  • @Remy: Is the thread hanging in the memory stream? I would expect the thread is hanging in the underliying THTTPRequest API. As soon the Rest server sends one byte I'm able to stop a request that was started asynchronous with BeginGet. – Schneider Infosystems Ltd Jun 29 '18 at 06:32
  • @Günter: The following changes leads to a hang in `fThread.Free` until the Rest server sends a next byte `procedure TMyRestAPI.StartRequest(const URL: string); ... fThread := TThread.CreateAnonymousThread(...); fThread.FreeOnTerminate := false; fThread.Start; end;` In the StopRequest `if assigned(fThread) then begin fThread.Terminate; x> fThread.Free; fThread := nil; fClient.Free;fStream.Free;` – Schneider Infosystems Ltd Jun 29 '18 at 07:06
  • @SchneiderInfosystemsLtd if you can gain access to the underlying socket, you can close it. That will usually abort any blocking socket read/write operation in progress. I'm not sure if you can do that with Embarcadero's `THTTPClient`, but you can with Indy's `TIdHTTP`, for instance. – Remy Lebeau Jun 29 '18 at 07:14
  • @Remy:Yes, that is exactly the question: How we abort a blocking socket within `THTTPClient` which serves different plattforms. I didn't found a possiblity. – Schneider Infosystems Ltd Jun 29 '18 at 08:22
  • @SchneiderInfosystemsLtd then I suggest you switch from `THTTPClient` to `TIdHTTP` (which is also cross-platform) or any other HTTP library/framework that actually suits your needs. – Remy Lebeau Jun 29 '18 at 09:31
  • Because of using https protocol the `TIdHTTP` together with OpenSSL was not my first choice. I have entered now an issue in the embarcadero quality central: https://quality.embarcadero.com/browse/RSP-20827 – Schneider Infosystems Ltd Jul 02 '18 at 07:34
  • I believe the only solution is to let the thread terminate gracefully. If you need to start another request while the other thread is still hanging, spawn a new thread (and preferably use a `TThreadPool`). Another way of last resort would be delegating the http(s) communication to a separate process that can be killed so the OS (probably Windows only) cleans up after that. – Günther the Beautiful Jul 02 '18 at 08:39

0 Answers0