Blog
All Blog Posts | Next Post | Previous PostDiving deeper: Cloudbase REST in Delphi, Part 1/3: Basics
Thursday, September 22, 2022
Intro
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.
The "Diving Deeper: Cloudbase REST" blog series will consist out of three parts:
- Basics, getting to know Cloudbase REST
- Extended, various special REST specific requirements and how to implement them
- Sync vs Async operations
The blog series will use a test REST service. Note that when referring to unit names, the prefix of the framework ((FMX.)(VCL.)(WEBLib.)(LCL)) will not be included, for readability purposes. The code snippets are written in FMX as a default framework, but can easily be ported to other frameworks. If you have any questions during the blog series, don't hesitate to ask them in the comments section, or by using our support channels.
Units
After installing TMS FNC Core, the most important units are the TMSFNCCloudBase.pas and the TMSFNCUtils.pas unit.
- TMSFNCCloudBase.pas: Unit containing interfaces and functions to create and execute requests, and to capture and parse the result.
- TMSFNCUtils.pas: Unit containing a lot of class helper functions which, in some situations, are helpful to build the request with proper encoding, or to easily convert data to a specific type required for the REST request to be executed. Additionally, this unit contains class wrappers to parse JSON, which in many situations is the result string from the request.
Getting Started
We get started by adding the unit TMSFNCCloudBase. There is a class called TTMSFNCCloudBase, that provides the required functions and properties to setup a REST Request. To create an instance of TTMSFNCCloudBase, call
c := TTMSFNCCloudBase.Create;
c.Request.Clear; c.Request.Host := 'https://myhost.com'; c.Request.Method := rmGET; c.Request.Path := '/mypath/upload'; c.Request.Query := 'param1=value¶m2=value'; c.Request.PostData := 'MyPostData'; c.Request.ResultType := rrtString; //rrtStream //rrtFile c.Request.AddHeader('Header1', 'Header Data');
c.ExecuteRequest( procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult) begin //Parse ARequestResult end );
After executing the request, the callback is triggered. The ARequestResult of type TTMSFNCCloudBaseRequestResult will contain the result of the request. There are 3 types of result request content. The request result type needs to be set before executing the request.
- rrtString: The default return value of the request result. Can be XML, JSON, or any other type of text. The property ARequestResult.ResultString contains the value.
- rrtStream: Returns the content as a stream, the property ARequestResult.ResultStream contains the content
- rrtFile: Immediately saves the content to a file, specified by c.Request.ResultFile before executing the request.
Supported Resource Methods
Below is a list of supported resource methods in TTMSFNCCloudBase
- rmGET: The GET method is used to read/retrieve the content of a resource.
- rmPOST: The POST method is most-often utilized to create new resources.
- rmPOSTMULTIPARTRELATED,
- rmPOSTMULTIPART: Same as POST, but with a special multi-part post data body for sending special request content such as images, files or any other binary content.
- rmPUT: PUT is most-often utilized for updating resources.
- rmPUTMULTIPART,
- rmPUTMULTIPARTRELATED: Same as PUT, but with a special multi-part post data, similar to POST multipart.
- rmDELETE: The DELETE method is used to delete a resource.
- rmPATCH: The PATCH method is used to modify a resource, mostly an incremental difference between the original resource and the new resource.
- rmUPDATE: An equivalent for the rmPUT resource method.
For testing purposes we use httpbin.org which is a service to test REST requests. The first part of this blog post is not going to cover all of the above resource methods. In this blog post we are going to cover the GET and POST methods and the next blog post will go a bit deeper and will cover a couple of special cases.
GET
To execute a GET request we use the following code.
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) begin if ARequestResult.Success then Memo1.Text := ARequestResult.ResultString else Memo1.Text := 'Request failed'; end );
- Credentials are missing such as API key, client-id & secret which are required for services that require authentication
- URL is malformed
- Data is not correctly formatted or is missing required parameters
- ...
When executing the request, and the request succeeds, we get back JSON. the JSON contains information about the request.
{ "args": {}, "headers": { "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-63246a5a-341eb67801e5c0f00897996f" }, "origin": "X", "url": "https://httpbin.org/get" }
For example, if we would change the request to include query parameters like the code below:
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 );
{ "args": { "param1": "Hello World", "param2": "My First Request" }, "headers": { "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-63246d6a-00d0a592795a4fcd73753f6d" }, "origin": "X", "url": "https://httpbin.org/get?param1=Hello World¶m2=My First Request" }
c.Request.Query := 'param1=Hello%20World¶m2=My%20First%20Request';
to
c.Request.Query := 'param1=' + TTMSFNCUtils.URLEncode('Hello World') + '¶m2=' + TTMSFNCUtils.URLEncode('My First Request');
POST
As explained a POST request is sending content to the service. For httpbin.org we can send any content we want, but other services typically require specific data information and in a particular format. To send data to the service we use the PostData property. Below is an example
c.Request.Clear; c.Request.Host := 'https://httpbin.org'; c.Request.Method := rmPOST; c.Request.Path := '/post'; c.Request.PostData := '{"MyData":"Hello World"}'; 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 );
{ "args": {}, "data": "{\"MyData\":\"Hello World\"}", "files": {}, "form": {}, "headers": { "Cache-Control": "no-cache", "Content-Length": "24", "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-632473d8-5d71c7d1106087a277a7713b" }, "json": { "MyData": "Hello World" }, "origin": "X", "url": "https://httpbin.org/post" }
Helper Methods
In the cloud base unit there are a couple of helper methods/functions available to easily setup a rest request. This technique can be used to download a file, or request data with a simple request to an URL. Below is a list of methods or functions that can be used to achieve this.
function HTTPPostDataBuilder: TTMSFNCCloudBaseRequestPostDataBuilder; procedure HTTPClearHeaders; procedure HTTPAddHeader(const AName: string; const AValue: string); procedure HTTPCloudRequest(const AURL: string; AResultType: TTMSFNCCloudBaseRequestResultType = rrtString; AMethod: TTMSFNCCloudBaseRequestMethod = rmGET; const ARequestResultEvent: TTMSFNCCloudBaseRequestResultEvent = nil); overload; procedure HTTPCloudRequest(const AHost, APath, AQuery, APostData: string; AResultType: TTMSFNCCloudBaseRequestResultType = rrtString; AMethod: TTMSFNCCloudBaseRequestMethod = rmPOST; const ARequestResultEvent: TTMSFNCCloudBaseRequestResultEvent = nil); overload;
TTMSFNCCloudBase.DownloadFileFromURL('https://www.myserver.com/myfile.zip', procedure(const ARequestResult: TTMSFNCCloudBaseRequestResult) begin ARequestResult.ResultStream.SaveToFile('myfile.zip'); end);
The class method DownloadFileFromURL will download the file specified as a parameter to a memory stream (ARequestResult.ResultStream) and then allow to save it to a file.
Feedback
Next up will be a more extended guide around REST and TTMSFNCCloudBase, 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 received 4 comments.
As for his encoding argument, again I think this library handles it very well. I have had to interact with several REST services and there are instances where the encoding varies.
Delphi does in fact have many modern features and, in my opinion, the TMS FNC CloudBase library is very well designed and takes advantage of some of those features.
But hey, to each their own. I personally love the way Delphi does things, the verbosity makes the code self documenting and that is a form of elegance itself. And TMS makes excellent libraries that are just plain easy to use.
Matthew Vesperman
Bruno Fierens
Aside from that, there are easy convenient functions / class helpers that haven’t been mentioned here like the
SimpleGETAsString(‘https://httpbin.org/get’);
Which is even easier than the Python implementation. You can even choose to run it synchronous or asynchronous.
The TTMSFNCCloudBaseRequestResult is an object containing all information you need from the request and the response: headers, status code, result stream or string. No need to search for anything else.
I would say, give it a chance. It handles easy things and can also handle more complex cases.
Pieter Scheldeman
All Blog Posts | Next Post | Previous Post
I know Delphi is a terrible language to design elegant APIs in since it lacks many modern features, but couldn''t you have studied the best REST libraries available across many languages and tried to model after one or several of them?
Again, I know the problem here is more Delphi than TMS, but your first httpbin get example would look like this in Python using the Requests library:
import requests
c = requests.get("https://httpbin.org/get")
if c.status_code == requests.codes.success:
print(c.text)
else:
print("Request failed")
Folks, no human being is ever going to, NOR SHOULD THEY EVER HAVE TO, remember "TTMSFNCCloudBaseRequestResult". That''s positively Java-ish. The native Delphi request library is even worse, requiring users to remember "TRESTRequestParameterKind.pkGETorPOST".
The Requests library does it right. a function for each method. Delphi libraries all seem to create one mega-object that needs creating, executing, destroying.
In your example you need to create an object, clear the object for some reason, set the method, set the result type for some baffling reason (shouldn''t that be determined by what actually comes back?), call an execute method and for some reason I can''t fathom write a result handler INSIDE the execute method which includes memorizing "TTMSFNCCloudBaseRequestResult". Why the heck not just put the status code inside the object and let the user check it?
Another head-scratcher:
c.Request.Query := ''param1='' + TTMSFNCUtils.URLEncode(''Hello World'') + ''¶m2='' + TTMSFNCUtils.URLEncode(''My First Request'');
None of you see how verbose, bloated and ugly that is?
Why not just AUTOMATICALLY encode the URL? Again, the following example is nicer than it ever could be in Delphi because Python has dictionary literals, but the Requests library would handle the parameters like this:
requests.get("https://httpbin.org/get", params={"param1": "Hello world", "param2": "My first request"})
Note no need to build the URL yourself, no need to manually encode/escape anything or build a string studded with "TTMSFNCUtils.URLEncode".
You folks make a lot of nice software, but the interface being offered for this REST library is rather weird. Even with Delphi''s limitations in mind a lot of the design doesn''t make sense (the anonymous function in particular).
I can''t imagine choosing to do a lot of REST work with this library (or really any Delphi REST library). So much manual work that could/should be handled by libraries, especially when it''s a commercial library.
Joseph