Blog

All Blog Posts  |  Next Post  |  Previous Post

Diving deeper: Cloudbase REST in Delphi, Part 2/3: Extended

Tuesday, September 27, 2022

TMS Software Delphi  Components

Intro

Today, we are continuing our cloud base REST blog series with part 2: Extended. If you missed our first blog, follow this link: https://www.tmssoftware.com/site/blog.asp?post=993. TMS FNC Core is a universal core layer for creating rich visual and non-visual components for VCL, FMX, LCL and WEB core apps. A major part of TMS FNC Core is the ability to create and execute REST requests. In this blog post we will take a look at some special cases when handling REST requests and responses.

Encoding

When working with REST requests, encoding is something that will be encountered at some point during development. The most common encoding types are URL & Base64 encoding. There are a few others but are not frequently encountered. Let's take a look at URL encoding first.

URL encoding

Typically for GET requests the URL has query parameters. Some of those parameters can contain special characters that are not accepted by default when executing the request. Special characters in URL should be encoded. More info can be found here: https://www.w3schools.com/tags/ref_urlencode.ASP. Now let's apply this to our request example from the previous blog post.

c.Request.Clear;
c.Request.Host := 'https://httpbin.org';
c.Request.Method := rmGET;
c.Request.Path := '/get';
c.Request.Query := 'param1=Hello%20World&param2=My%20First%20Request';
c.Request.ResultType := rrtString;
c.ExecuteRequest(
  procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult)
  begin
    if ARequestResult.Success then
      Memo1.Text := ARequestResult.ResultString
    else
      Memo1.Text := 'Request failed';
  end
  );

As you can see, the Query property is filled with 2 parameters which are encoded. In this case, spaces are not accepted, and are converted to %20. Now to make this convenient, the TTMSFNCUtils class has a class function that encodes URL parameters as shown in the sample below.

c.Request.Query := 'param1=' + TTMSFNCUtils.URLEncode('Hello World') + '&param2=' + TTMSFNCUtils.URLEncode('My First Request');

Base64 encoding

In some situations, when executing requests, Base64 encoding is required. Some services are accepting a username and password (or client ID and secret) and sending them in plain text means basically anyone can compromise your account. In the following example, we add an Authorization header, which is required by the service, in order to authorize via a user credentials. As explained, we cannot pass this as plain text, so we have to encode it.

c.Request.Clear;
c.Request.Host := 'https://httpbin.org';
c.Request.Method := rmGET;
c.Request.Path := '/get';
c.Request.AddHeader('Authorization', 'Basic ' + TTMSFNCUtils.Encode64('MyUserName' + ':' + 'MyPassWord'));
c.Request.ResultType := rrtString;
c.ExecuteRequest(
  procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult)
  begin
    if ARequestResult.Success then
      Memo1.Text := ARequestResult.ResultString
    else
      Memo1.Text := 'Request failed';
  end
  );

After executing the request we get back JSON, and clearly see the header of the request contains the Base64 encoded Authorization value. The server has accepted this. In the httpbin.org service, there is no real validation being done, the service just returns the request information as a response, but other services will typically return an error or a succes during the authorization validation. Looking at the returned JSON we can see the base64 encoded Authorization header.

{
  "args": {}, 
  "headers": {
    "Authorization": "Basic TXlVc2VyTmFtZTpNeVBhc3NXb3Jk", 
    "Cache-Control": "no-cache", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101", 
    "X-Amzn-Trace-Id": "Root=1-63298b33-3620d6a00546f10f2d671d8a"
  }, 
  "origin": "X", 
  "url": "https://httpbin.org/get"
}

Retrieving Response Headers

The response coming back from the service contains headers. In some REST services, the headers might contain essential information about the request. To retrieve the headers, we can write

c.Request.Clear;
c.Request.Host := 'https://httpbin.org';
c.Request.Method := rmGET;
c.Request.Path := '/get';
c.Request.ResultType := rrtString;
c.ExecuteRequest(
  procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult)
  var
    r: TTMSFNCCloudBaseRequestHeader;
    I: Integer;
  begin
    if ARequestResult.Success then
    begin
      for I := 0 to ARequestResult.ResponseHeaders.Count - 1 do
      begin
        r := ARequestResult.ResponseHeaders[I];
        Memo1.Lines.Add('Name: ' + r.Name + ' Value: ' + r.Value);
      end;
    end
    else
      Memo1.Text := 'Request failed';
  end
  );

The response headers returning from the service are

Name: "", Value: "HTTP/1.1 200 OK"
Name: "Date", Value: "Tue, 20 Sep 2022 10:52:47 GMT"
Name: "Content-Type", Value: "application/json"
Name: "Content-Length", Value: "321"
Name: "Connection", Value: "keep-alive"
Name: "Server", Value: "gunicorn/19.9.0"
Name: "Access-Control-Allow-Origin", Value: "*"
Name: "Access-Control-Allow-Credentials", Value: "true"

The headers typically contain the response status code, date, the content type and length and server connection info. In some occasions, headers can contain additional information that is useful for parsing the request.

PostData Builder

When executing a POST request this typically requires content to be added. In a simple form this is JSON content, which can just be a string concatenation added to the PostData property of the request. In some situations, a service might require a file to be uploaded, and in most cases this is done with a multi-part upload mechnism (at least for smaller files). This means that, not only JSON containing the file name and description need to be in the post-data, the file itself will be part of the PostData body in a binary form. This is where the PostData Builder comes in. Let's take a look at the followin example, where the PostData is constructed to upload a file.

c.Request.Clear;
c.Request.Host := 'https://httpbin.org';
c.Request.Method := rmPOSTMULTIPART;
c.Request.Path := '/post';

  ct := '{'
    + '"name": "' + 'MyFile.png' + '"'
    + ', "description": "' + 'My File' + '"'
    + '}';

c.PostDataBuilder.Clear;
c.PostDataBuilder.AddText('Content-Type: application/json; charset=UTF-8');
c.PostDataBuilder.AddLineBreak;
c.PostDataBuilder.AddText(ct);
c.PostDataBuilder.AddHeadBoundary;
c.PostDataBuilder.AddText('Content-Disposition: form-data; name="file"; filename="' + 'MyFile.png' + '"');
c.PostDataBuilder.AddContentType('application/octet-stream');
c.PostDataBuilder.AddLineBreak;
c.Request.PostData := c.PostDataBuilder.Build;
c.Request.CustomHeaders := True;
c.Request.UploadFile := 'MyDrive:\Log File.zip';

c.ExecuteRequest(
  procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult)
  begin
    if ARequestResult.Success then
      Memo1.Text := ARequestResult.ResultString
    else
      Memo1.Text := 'Request failed';
  end
  );
When the request is prepared, it will concatenate all relevant pieces of information required for the PostData body through the PostDataBuilder. When executing the request, internally three things will happen.

  1. Connection with the host is made, and prepared to send content through a multi-part upload POST resource
  2. The post-data is send to the server alongside the file (UploadFile)
  3. The request monitors file progress and eventually triggers the callback

When the callback is triggered, we get the following result.

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Cache-Control": "no-cache", 
    "Content-Length": "1348512", 
    "Content-Type": "multipart/form-data; boundary=AaB03x", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101", 
    "X-Amzn-Trace-Id": "Root=1-6329c7ad-1b2c462e1d2b2487341c4cd5"
  }, 
  "json": null, 
  "origin": "X", 
  "url": "https://httpbin.org/post"
}
From the return data, you can clearly see the length of the content matches the data in the PostData property in combination with the size of the file that is uploaded. In httpbin.org we cannot check if the file is uploaded effectively as this is a REST request test service, not a storage service, but when applying this to a cloud storage service such as Google Drive, the file will be uploaded. Each service has various specifications which can be found in the API documentation.

Feedback

Our last blog will cover how to execute a request synchronously and explain the difference with asynchronous requests, so stay tuned for more to come! As always, please leave a comment or if you have any questions, don't hesitate to ask us!



Pieter Scheldeman


  1. Diving deeper: Cloudbase REST in Delphi, Part 1/3: Basics

  2. Diving deeper: Cloudbase REST in Delphi, Part 2/3: Extended

  3. Diving deeper: Cloudbase REST in Delphi, Part 3/3: Sync vs Async



This blog post has not received any comments yet.



Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post