A+ A A-

Getting started with Azure Function Apps in Delphi

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.

Authors opinion:
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.

Not too long ago, Microsoft added support for running a native compiled binary, granted it follow certain conventions, as a Function App. They demonstrated this by showing how you could implement a custom http trigger in Go and Rust, both compiled languages, however these languages don’t offer the same advantages as Object Pascal does. This means you’re not limited to a managed language like C# or interpreted languages like JavaScript or Python.

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.

Once the prerequisites are installed, either clone the demo projects from this GitHub repository, or set up a new blank Azure Function Apps custom handler project, following the guide outlined here

For project management simplicity I would encourage you to follow this folder structure when creating an Azure Function App in Delphi:

FuncApp1

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.


FuncApp2

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:

BindPort()

CheckPort()

StartServer()

StopServer()

RunServer()

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.


FuncApp3

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!



We use cookies on our website. Some of them are essential for the operation of the site, while others help us to improve this site and the user experience (tracking cookies). You can decide for yourself whether you want to allow cookies or not. Please note that if you reject them, you may not be able to use all the functionalities of the site.