In extension to a recent Webinar abut the same topic I did together with Stephen Ball from Embarcadero (Watch here), I’ve condensed and added additional information I was unable to show during the initial Webinar in the following blog post. This is intended as a primer to get started with Azure Function Apps and Delphi.
Azure Function Apps is a handy component in the category of “serverless”, part of the Microsoft Azure Cloud Platform.
Personally I don’t subscribe to the buzzword “Serverless” as we do need servers to make things work, rather it’s a managed event driven framework which abstracts some of the manual labor away with dynamic resource allocation and so on.
“Serverless” is a sellable buzzword for less management cost – but it does require servers at the end of the day.
Call it what it is - managed event driven modules.
Plan accordingly, and evaluate carefully, as “Serverless” isn’t the holy grail for every scenario.
In a nutshell, Azure Function Apps are self-contained web servers that respond to an event – an api endpoint and allows you to scale up and down based on demand without you having to worry about a lack of resources.
In production you can either target a 64-bit Linux or Windows Function App Runtime – thankfully Delphi conveniently have compilers for both targets!
Before we get started, we need to install a few prerequisites. To avoid “Don’t Repeat Yourself”, I will be linking to setup guides from the Microsoft documentation, as they already have covered the basics well. The first section is setting up your environment, but in essence we need:
- Azure Functions Core Tools – A local version of the Function Apps runtime also found in the cloud.
- In this demo I’m using the 32-bit version of the Function Core Tools framework, but for production it is 64-bit only.
- Visual Studio Code – For the time being, until my RAD Studio Azure plugin is ready, VS Code has the Azure Extension which makes it easier to create and deploy Function Apps.
- Azure Functions Extension for VS Code – Connects to your Azure Cloud account and makes it easier to create and deploy Azure Functions
As we’re using Delphi, we won’t need either the Go or Rust elements.
For project management simplicity I would encourage you to follow this folder structure when creating an Azure Function App in Delphi:
DelphiFunctionApp – This folder contains the actual Delphi project. The project file has been set up to output the binary one level above. Go to Project Options -> Building -> Delphi Compiler and set the Output directory to ..\
DelphiTrigger – This folder contains the Azure Function descriptor “function.json” which instructs the Function Runtime how to handle this custom trigger and what methods the function app supports.
“host.json” – This file instructs the Function Runtime how to load our custom HTTP trigger (Our Delphi app) – the “defaultExecutablePath” and in this case, also instructs the Function Runtime to forward the HTTP requests with the setting “enableForwardingHttpRequest”: true – for our purpose it greatly simplifies development.
Remember to rename the name of the executable according to what Function App Runtime stack (Windows or Linux) you’re executing on in the production environment.
For now, we won’t focus on the rest of the files listed in the directory.
Now let’s write some Delphi code!
In these demo projects, I’ll use the Standalone WebBroker project template built directly into Delphi, based around the Indy components. For a future post, we will be investigating other compact webserver offerings for Delphi.
The first demo project, “Example001_SimpleHelloWorld” we will start with an empty WebBroker project, trim out unnecessary code, add a Linux target for convenience, and test that the local Function Runtime is able to execute the code and emit a HelloWorld JSON string to the browser.
In the second demo project, “Example002_ParsingRequestParameters” we’ll explore how to parse provided URL parameters and return a response based on the parsed input.
If you don’t set the enableForwardingHttpRequest option to true, the Function Runtime will send a JSON payload to your http handler (your Delphi app). In a future blog post we’ll explore how you can parse this payload and manage the data received.
Common for a standalone console webbroker template, is the interactive menu.
We want the web server component to boot up immediately, so we are going to cut out the additional fluff added, which leaves us with the methods:
And the main application entry point.
Handling the request happens in the default webmodule created in the DefaultHandlerAction.
procedure TwmAzureFunction.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); const cJSON = 'application/json'; var LJSONObject: TJSONObject; begin LJSONObject := TJSONObject.Create; try LJSONObject.AddPair('HelloWorldFunction', TJSONBool.Create(False)); if Request.PathInfo.Equals('/api/DelphiTrigger') then begin Response.ContentType := cJSON; Response.Content := LJSONObject.ToJSON; FunctionRequested := True; end; finally LJSONObject.Free; end; end;
Once you have implemented your DefaultHandlerAction and everything is set up, compile the project to produce an exe file.
Open a command prompt or PowerShell window, and navigate to the root folder of the Function Apps project (Where you have the host.json file). Run the command: func start --verbose
If everything is set up, the Function Apps Runtime will boot up and load our custom HTTP trigger and webbroker executable.
Try opening a browser and navigate to the URL listed in the output of your command prompt / PoweShell window.
You should see the Function App Runtime receiving the request, forwarding it to our Delphi app and return the response our Delphi app forwarded.
In our second example, where we handle the parameters, provided in the URL, we reuse most of our existing hello world example.
The change is how the parameter URL is provided, and how we handle the Request in our WebModule.
By modifying our DefaultHandlerAction we can check whether additional queries have been added.
If we provide these parameters here “?Name=Glenn&Age=32&Adult=True” appended to our trigger, we get a valid JSON response.
If we’re not providing the correct parameters, we’ll get an error.
procedure TwmAzureFunction.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); const cJSON = 'application/json'; var LJSONObject: TJSONObject; begin LJSONObject := TJSONObject.Create; try if Request.PathInfo.Equals('/api/DelphiTrigger') then begin if Request.QueryFields.Count = 3 then begin LJSONObject.AddPair('PersonName', TJSONString.Create(Request.QueryFields.Values['Name'])); LJSONObject.AddPair('PersonAge', TJSONNumber.Create(Request.QueryFields.Values['Age'].ToDouble)); LJSONObject.AddPair('PersonIsAdult', TJSONBool.Create(Request.QueryFields.Values['Adult'].ToBoolean)); end else LJSONObject.AddPair('Error', 'Not enough parameters'); Response.ContentType := cJSON; Response.Content := LJSONObject.ToJSON; FunctionRequested := True; end; finally LJSONObject.Free; end; end;
For greater control, you can even send JSON objects!
Reach out to me on Twitter if you have further questions!