Blog
All Blog Posts | Next Post | Previous PostDiving deeper: Cloudbase REST in Delphi, Part 2/3: Extended
Tuesday, September 27, 2022
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¶m2=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') + '¶m2=' + 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 );
- Connection with the host is made, and prepared to send content through a multi-part upload POST resource
- The post-data is send to the server alongside the file (UploadFile)
- 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" }
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
Related Blog Posts
-
Diving deeper: Cloudbase REST in Delphi, Part 1/3: Basics
-
Diving deeper: Cloudbase REST in Delphi, Part 2/3: Extended
-
Diving deeper: Cloudbase REST in Delphi, Part 3/3: Sync vs Async
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post