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.
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.
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.
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.
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.
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
ExecuteRequestWithResultStreamwhileResultTypeisrrtStringgives you an empty stream. Use the typed method (which sets the type) or setResultTypeto match. rrtFilewithoutResultFile. A file result with no destination path has nowhere to write. Always setResultFilefirst.- 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.