Skip to main content
Table of Contents

Executing requests and result types

Once a request is configured, you execute it and collect the response in one of three shapes: text, a stream, or a file. Each shape has a matching execute method and a matching ResultType. Execution is asynchronous by default, so the call returns immediately and the response is delivered through a callback or event. This chapter explains the execution modes and the three result types, and how to chain one call into the next.

Synchronous and asynchronous execution

Async defaults to True: the execute method returns at once and the response arrives later in a callback or response event — the right choice for UI applications, where blocking the main thread would freeze the window. Set Async := False only in console tools or on a background thread; the executed request then already holds the response when the call returns.

Choose asynchronous or synchronous executionPascalC++
procedure TForm1.ExecuteAsyncOrSync;
var
  LExecuted: TTMSFNCRESTClientExecutedRequest;
begin
  TMSFNCRESTClient1.Request.URL := 'https://jsonplaceholder.typicode.com/posts/1';

  { Async (the default, True) returns immediately; read the result in a
    callback or response event. Keep this for UI applications. }
  TMSFNCRESTClient1.Request.Async := True;
  TMSFNCRESTClient1.ExecuteRequestWithResultString(
    procedure(AResponseString: string)
    begin
      Memo1.Lines.Text := AResponseString;
    end);

  { Async := False blocks until the response is in. The returned executed
    request already holds the response, so no callback is needed. Use it
    only off the main thread or in console/tool code. }
  TMSFNCRESTClient1.Request.Async := False;
  LExecuted := TMSFNCRESTClient1.ExecuteRequestWithResultString;
  Memo1.Lines.Text := LExecuted.Response.ResponseString;
end;
// Anonymous methods are interfaces in C++Builder: implement the callback in a class.
class TShowString : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseStringCallBack>
{
public:
  TMemo *Target;
  void __fastcall Invoke(System::UnicodeString AResponseString)
  {
    Target->Lines->Text = AResponseString;
  }
};

void __fastcall TForm1::ExecuteAsyncOrSync()
{
  TMSFNCRESTClient1->Request->URL = "https://jsonplaceholder.typicode.com/posts/1";

  // Async (the default, true) returns immediately; read the result in a
  // callback or response event. Keep this for UI applications.
  TMSFNCRESTClient1->Request->Async = true;
  TShowString *Callback = new TShowString();
  Callback->Target = Memo1;
  TMSFNCRESTClient1->ExecuteRequestWithResultString(_di_TTMSFNCRESTClientGetResponseStringCallBack(Callback));

  // Async = false blocks until the response is in. The returned executed
  // request already holds the response, so no callback is needed. Use it
  // only off the main thread or in console/tool code.
  TMSFNCRESTClient1->Request->Async = false;
  TTMSFNCRESTClientExecutedRequest *LExecuted = TMSFNCRESTClient1->ExecuteRequestWithResultString();
  Memo1->Lines->Text = LExecuted->Response->ResponseString;
}

Warning

Never use Async := False on the main UI thread. A slow or unreachable server will hang the application until the request times out.

Reading a text response

For JSON, XML, or any text payload, set ResultType := rrtString and call ExecuteRequestWithResultString. You can pass an inline callback that receives the body, in addition to the design-time event.

Read a text response with an inline callbackPascalC++
procedure TForm1.ReadStringResult;
begin
  TMSFNCRESTClient1.Request.ResultType := rrtString;
  TMSFNCRESTClient1.Request.URL := 'https://jsonplaceholder.typicode.com/posts/1';

  { Pass a callback directly to the execute method. It runs when the
    response arrives, in addition to any OnRequestResponseStringRetrieved
    handler assigned at design time. }
  TMSFNCRESTClient1.ExecuteRequestWithResultString(
    procedure(AResponseString: string)
    begin
      Memo1.Lines.Text := AResponseString;
    end);
end;
// Anonymous methods are interfaces in C++Builder: implement the callback in a class.
class TShowString : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseStringCallBack>
{
public:
  TMemo *Target;
  void __fastcall Invoke(System::UnicodeString AResponseString)
  {
    Target->Lines->Text = AResponseString;
  }
};

void __fastcall TForm1::ReadStringResult()
{
  TMSFNCRESTClient1->Request->ResultType = rrtString;
  TMSFNCRESTClient1->Request->URL = "https://jsonplaceholder.typicode.com/posts/1";

  // Pass a callback directly to the execute method. It runs when the
  // response arrives, in addition to any OnRequestResponseStringRetrieved
  // handler assigned at design time.
  TShowString *Callback = new TShowString();
  Callback->Target = Memo1;
  TMSFNCRESTClient1->ExecuteRequestWithResultString(_di_TTMSFNCRESTClientGetResponseStringCallBack(Callback));
}

Downloading into a stream

For binary content — images, archives, anything you keep in memory — set ResultType := rrtStream and call ExecuteRequestWithResultStream. The callback receives a TMemoryStream ready to load.

Download binary content into a streamPascalC++
procedure TForm1.DownloadImage(const AImageURL: string);
begin
  TMSFNCRESTClient1.Request.Clear;
  TMSFNCRESTClient1.Request.ResultType := rrtStream;
  TMSFNCRESTClient1.Request.URL := AImageURL;

  { The stream callback receives a TMemoryStream positioned at the start of
    the response body, ready to load. }
  TMSFNCRESTClient1.ExecuteRequestWithResultStream(
    procedure(AResponseStream: TMemoryStream)
    begin
      Image1.Bitmap.LoadFromStream(AResponseStream);
    end);
end;
// Anonymous methods are interfaces in C++Builder: implement the callback in a class.
class TImageLoaded : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseStreamCallBack>
{
public:
  TImage *Target;
  void __fastcall Invoke(System::Classes::TMemoryStream *AResponseStream)
  {
    Target->Bitmap->LoadFromStream(AResponseStream);
  }
};

void __fastcall TForm1::DownloadImage(const System::UnicodeString AImageURL)
{
  TMSFNCRESTClient1->Request->Clear();
  TMSFNCRESTClient1->Request->ResultType = rrtStream;
  TMSFNCRESTClient1->Request->URL = AImageURL;

  // The stream callback receives a TMemoryStream positioned at the start of
  // the response body, ready to load.
  TImageLoaded *Callback = new TImageLoaded();
  Callback->Target = Image1;
  TMSFNCRESTClient1->ExecuteRequestWithResultStream(_di_TTMSFNCRESTClientGetResponseStreamCallBack(Callback));
}

Saving a response to a file

To write a download straight to disk without holding it in memory, set ResultType := rrtFile, set ResultFile to the destination path, and call ExecuteRequestWithResultFile. The callback reports the final path and byte count.

Save a response directly to a filePascalC++
procedure TForm1.DownloadToFile;
begin
  TMSFNCRESTClient1.Request.Clear;
  TMSFNCRESTClient1.Request.ResultType := rrtFile;

  { ResultFile is the destination path the response body is written to. }
  TMSFNCRESTClient1.Request.ResultFile := TPath.Combine(TPath.GetDownloadsPath, 'report.pdf');
  TMSFNCRESTClient1.Request.URL := 'https://api.example.com/reports/42.pdf';

  TMSFNCRESTClient1.ExecuteRequestWithResultFile(
    procedure(AResponseFile: string; AResponseFileSize: Integer)
    begin
      ShowMessage(Format('Saved %d bytes to %s', [AResponseFileSize, AResponseFile]));
    end);
end;
// Anonymous methods are interfaces in C++Builder: implement the callback in a class.
class TFileSaved : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseFileCallBack>
{
public:
  void __fastcall Invoke(System::UnicodeString AResponseFile, int AResponseFileSize)
  {
    ShowMessage(Format("Saved %d bytes to %s", ARRAYOFCONST((AResponseFileSize, AResponseFile))));
  }
};

void __fastcall TForm1::DownloadToFile()
{
  TMSFNCRESTClient1->Request->Clear();
  TMSFNCRESTClient1->Request->ResultType = rrtFile;

  // ResultFile is the destination path the response body is written to.
  TMSFNCRESTClient1->Request->ResultFile = System::Ioutils::TPath::Combine(System::Ioutils::TPath::GetDownloadsPath(), "report.pdf");
  TMSFNCRESTClient1->Request->URL = "https://api.example.com/reports/42.pdf";

  TMSFNCRESTClient1->ExecuteRequestWithResultFile(_di_TTMSFNCRESTClientGetResponseFileCallBack(new TFileSaved()));
}

Note

The plain ExecuteRequest honours whatever ResultType you set on the request, and its OnRequestResponseRetrieved event exposes all three targets (string, stream, and file) at once. The typed ExecuteRequestWith... methods are shortcuts that also set the matching ResultType for you.

Chaining requests together

Real workflows often run one request based on another's result — fetch a record, then download an image it references. The example combines a text request and a stream request: it parses the first JSON response, extracts the thumbnailUrl, and feeds it into a second, stream-based call.

Chain a JSON request into a binary downloadPascalC++
procedure TForm1.LoadPhotoThumbnail(APhotoID: Integer);
begin
  { Step 1 - a string request returns a JSON record. }
  TMSFNCRESTClient1.Request.Clear;
  TMSFNCRESTClient1.Request.ResultType := rrtString;
  TMSFNCRESTClient1.Request.Host := 'https://jsonplaceholder.typicode.com';
  TMSFNCRESTClient1.Request.Path := '/photos/' + IntToStr(APhotoID);

  TMSFNCRESTClient1.ExecuteRequestWithResultString(
    procedure(AResponseString: string)
    var
      LJSON: TJSONValue;
      LImageURL: string;
    begin
      LJSON := TTMSFNCUtils.ParseJSON(AResponseString);
      if not Assigned(LJSON) then
        Exit;
      try
        LImageURL := TTMSFNCUtils.GetJSONProp(LJSON, 'thumbnailUrl');
        { Step 2 - feed the extracted URL into a stream request. }
        if LImageURL <> '' then
        begin
          TMSFNCRESTClient1.Request.Clear;
          TMSFNCRESTClient1.Request.ResultType := rrtStream;
          TMSFNCRESTClient1.Request.URL := LImageURL;
          TMSFNCRESTClient1.ExecuteRequestWithResultStream(
            procedure(AResponseStream: TMemoryStream)
            begin
              Image1.Bitmap.LoadFromStream(AResponseStream);
            end);
        end;
      finally
        LJSON.Free;
      end;
    end);
end;
// Anonymous methods are interfaces in C++Builder: implement the callbacks in classes.
class TThumbnailLoaded : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseStreamCallBack>
{
public:
  TImage *Target;
  void __fastcall Invoke(System::Classes::TMemoryStream *AResponseStream)
  {
    Target->Bitmap->LoadFromStream(AResponseStream);
  }
};

class TPhotoRecordLoaded : public TCppInterfacedObject<TTMSFNCRESTClientGetResponseStringCallBack>
{
public:
  TTMSFNCRESTClient *Client;
  TImage *Target;
  void __fastcall Invoke(System::UnicodeString AResponseString)
  {
    System::Json::TJSONValue *LJSON = TTMSFNCUtils::ParseJSON(AResponseString);
    if (LJSON == nullptr)
      return;
    try
    {
      System::UnicodeString LImageURL = TTMSFNCUtils::GetJSONProp(LJSON, "thumbnailUrl");
      // Step 2 - feed the extracted URL into a stream request.
      if (LImageURL != "")
      {
        Client->Request->Clear();
        Client->Request->ResultType = rrtStream;
        Client->Request->URL = LImageURL;
        TThumbnailLoaded *Next = new TThumbnailLoaded();
        Next->Target = Target;
        Client->ExecuteRequestWithResultStream(_di_TTMSFNCRESTClientGetResponseStreamCallBack(Next));
      }
    }
    __finally
    {
      delete LJSON;
    }
  }
};

void __fastcall TForm1::LoadPhotoThumbnail(int APhotoID)
{
  // Step 1 - a string request returns a JSON record.
  TMSFNCRESTClient1->Request->Clear();
  TMSFNCRESTClient1->Request->ResultType = rrtString;
  TMSFNCRESTClient1->Request->Host = "https://jsonplaceholder.typicode.com";
  TMSFNCRESTClient1->Request->Path = "/photos/" + IntToStr(APhotoID);

  TPhotoRecordLoaded *Callback = new TPhotoRecordLoaded();
  Callback->Client = TMSFNCRESTClient1;
  Callback->Target = Image1;
  TMSFNCRESTClient1->ExecuteRequestWithResultString(_di_TTMSFNCRESTClientGetResponseStringCallBack(Callback));
}

Common mistakes

  • Result type and execute method disagree. Calling ExecuteRequestWithResultStream while ResultType is rrtString gives you an empty stream. Use the typed method (which sets the type) or set ResultType to match.
  • rrtFile without ResultFile. A file result with no destination path has nowhere to write. Always set ResultFile first.
  • Reading the result on the wrong thread. With async execution, touch UI controls only inside the callback or response event, which run on the main thread — not right after the execute call returns.

See also