Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

Azure Function: A HttpTriger with a BlobInput in Java

--

Imagine you have created a set of tutorials and decide to host these on Azure Blob storage. Now you want authenticated users to be able to download this from their profiles. Having done a bit of investigation, you decide to use Azure Functions to implement this download feature for your website.

In this post, we will be looking at Azure Functions and answering the following questions, and also looking at an example implementation:

  • What is Azure Function?
  • How do we set up to develop Azure Functions locally?
  • How do I deploy my local Azure Function to Azure cloud?

What is Azure Function? 🤔 🤔

Azure Function(s) is a way for us to write simple functions that run in the cloud and do this without us having to create and/or configure the infrastructure for it to run on.

Oh okay! I understand the words but how does this all fit in?

Now, given we write code to perform or react to varying events and/or actions — from Web APIs that retrieve and/or update data when called to downloading a correct document for which a user has paid for.

To meet this need, Azure Functions provides on-demand computing by:

  • providing us with ways (via Azure Portal and SDKs) to write and deploy our feature logic through code blocks known as functions.
  • Ok, our function has now been written, [hopefully] tested, and deployed. Now, when requests to our function increase, Azure functions meet this demand with as many resources and function instances as required to handle the increase. Also as requests decrease, extra resources and function instances are removed.

I see…! So what programming languages can I use to write my block of code…functions?

We can write these function(s) in several programming languages including Java, C#, F#, Typescript, JavaScript, and a few others. The full list of supported languages can be found here.

In Java, we create a function using the @FunctionName annotation.

@FunctionName("download")
public HttpResponseMessage downloadFile(...) {
...
}

Nice!!! So any concepts I need to know to better understand and write my block of codes…functions?

Sure, there are a few. In this post, we will consider three key basic concepts.

Function App

A function app provides an execution context in which our block of code…function will run. Given this, a function can be thought of as a unit of deployment and management for our functions.

It is worth noting that a function app can have more than one function that is managed and scaled together.

Also, all functions in a function app must be written in the same programming language and share the same pricing plan and deployment approach.

Triggers

Remember our functions are basically blocks of code. Now, these blocks of code are wrapped as triggers to enable them to run.

A trigger specifies how a block of code (function) should run. A trigger has data associated with it. This data is usually the payload passed to the block of code.

It is worth noting that a function can have one and only one trigger.

A few trigger examples include

  • HTTP trigger. A function that will be run whenever it receives an HTTP request, responds based on the payload or query string. In our example, we will be taking a look at this;
  • Azure Blob Storage trigger. A function that will be executed whenever a blob is added to a specified container;
  • Timer trigger. A function that will be executed on a given schedule;
  • SendGrid. A function that will send a confirmation email when new items are added to a particular queue;
  • Azure Cosmos DB trigger. A function that will be executed whenever documents change in a document collection.

More details on triggers can be found in the Triggers and bindings section under Reference here

@FunctionName("download")
public HttpResponseMessage downloadFile(
@HttpTrigger(
name = "request",
methods = {GET},
authLevel = ANONYMOUS)
final HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
...
}

Bindings

This is a way of connecting our function to another resource (or service). Bindings can be input, output, or both.

Data from bindings are passed to our functions through parameters.

More on supported bindings for Azure functions can be found here.

In our example, we will be looking at how to pass a Blob storage as an input binding in our function.

Below is how we annotate blob storage as an input binding

public HttpResponseMessage downloadFile(
@BlobInput(
name = "file",
dataType = "binary",
path = "java-functions-container/test.png",
connection = "AzureWebJobsStorage")
byte[] content,
final ExecutionContext context) {
...
}

function.json file

This is a mandatory file that has a function’s definitions like a trigger, bindings (input, output, or both), and other configuration settings.

It is worth noting that

  • the runtime uses this config file to determine what events to monitor, how data should be passed to the parameters within a function, and what data should be returned when a function is executed;
  • in compiled languages, this file is generated automatically from annotations used in a block of code. And in scripting languages, this file must be supplied in our block of code.

For our example, the function.json file content is given below

{
"scriptFile" : "../java-azure-function-1.0.0-SNAPSHOT.jar",
"entryPoint" : "dev.etimbuk.functions.FileDownloadHttpTriggerFunction.downloadFile",
"bindings" : [ {
"type" : "httpTrigger",
"direction" : "in",
"name" : "req",
"methods" : [ "GET" ],
"authLevel" : "ANONYMOUS"
}, {
"type" : "blob",
"direction" : "in",
"name" : "file",
"path" : "java-functions-container/{filename}",
"dataType" : "binary",
"connection" : "AzureWebJobsStorage"
}, {
"type" : "http",
"direction" : "out",
"name" : "$return"
} ]
}

How?

Now that we have looked at functions, how they become a function app, what triggers and bindings are, let’s consider how we can create, write and deploy a Java function app to the Azure cloud platform.

Before we dive in, let us first know what tools we will need for this example and understand annotations used in defining a Java-based Azure Function with an HTTP trigger and a Blob storage input binding.

Tools ⚙️ ⚙️

  • CLI (Command Line Interface).
  • Azure CLI. This has to be installed locally and enables us to connect (or login) to our Azure subscription and deploy our function app to the Azure platform. Needed because as of writing we cannot edit a compiled language from the Azure portal;
  • Azure Functions Core Tools. This CLI-based tool allows us to develop and test our functions on our local computer.
  • Java 8 or 11 must be installed in your local environment and JAVA_HOME environment variable must be set
  • Gradle or Maven build tool.
  • An IDE. Currently, Azure function tooling exists for IntelliJ (IDE used for this post), Eclipse, Visual Studio Code, and Visual Studio (C# folks).
  • Postman. This is an optional tool that we use in testing our HTTPS requests. It’s optional as you pick your preferred option.

A few function annotations…

@FunctionName. This annotation is used to tell the Azure functions tooling the name of the annotated function when deployed to the Azure platform. For HttpTriggers, this basically defines our endpoint.

From the Azure portal, this will look like https://{YOUR_HOST/api/download} given our example.

@HttpTrigger. This annotation is applied to the Azure functions that will be triggered by a call to the HTTP endpoint specified by the function.

Recall our FunctionName defined endpoint…

@BlobInput. We use this annotation on a parameter whose value would come from a blob. The parameter type can be one of the following:

  • Any native Java types such as int, String, byte[];
  • Nullable values using Optional<T>; or
  • Any POJO type.

An example 💃 🕺

Phew!!! Enough with the text, now we look at our example.

A Brief

We have a user (or another service) hitting our API (an Azure function) to download a file in our example.

  • When the request is authorized, the function accesses the Blob storage (linked to it) to retrieve the file;
  • If the file is available from the storage container, this is sent back as a response to the user. If not, an appropriate error is returned.

Creating an Azure function…

There are several ways for creating an Azure function, in this post we will be looking at using the IntelliJ IDE with Azure Toolkit plugin installed with a mix of Gradle commands as our example will be Java focussed.

So let’s get started…

With IntelliJ

Here we use pictorial views to go through the steps used in creating an Azure Function with an HTTP trigger

  • Once the IntelliJ IDE has been opened, select File > New > Project…
Viewing having clicked File > New Project in IntelliJ
  • When step 4, from the above view, has been clicked
In this post, we are using Gradle as our Build tool

When step 10, from the above view, has been clicked

Naming our project.

Once your new Azure function project has been generated, you should see the below structure

java-azure-function project structure

Now that we have our first Azure function project generated for us, let us take a closer look at the azurefunction configuration in our build..gradle file.

azurefunctions {
subscription = '<your subscription id>'
resourceGroup = 'eau-java-functions-rg'
appName = 'java-azure-function-de5a' // please rename the function name
pricingTier = 'Consumption'
region = 'westus'
runtime {
os = 'windows'
javaVersion = '11'
}
}

This azurefunctions configuration allows us to specify different settings we require for our function app including

  • appName. This is the name of our function and has to be unique across the Azure platform and must be supplied with a value. To achieve a unique value in this post, I used, echo java-azure-function-$(openssl rand -hex 3)then copied over the echo-ed value;
  • resourceGroup. These required settings set the Azure resource group which our function will run;
  • region. This optional config sets the region in Azure to which we want our function deployed. If not supplied, this will default to westus
  • runtime.os The tells Azure which operating system we want our function deployed on
  • runtime.javaVersion This tells which java version should be used. As of writing the supported java version is either 8 or 11. Although given as optional, I will recommend adding as without it our function times out with an error similar to below
Error when java version not given in appSettings config

More configuration options can be found here.

The Code

At last, we now get to look at and understand the code that allows us to create an HTTP trigger with a blob input binding.

If you followed through and created the HTTP trigger using IntelliJ with the Azure plugin installed, you would have code similar to

Just the HttpTrigger…

Before we test and deploy this basic HTTP trigger (no blob input yet!), we will try and understand what it's actually doing

  • first is our function is named download. This is done using @FunctionName annotation;
  • second is the methods used within @HttpTrigger.
name. This is used in the function code for the request or request body;methods. This basically uses the http methods. In our example we are saying our function supports both GET and POST methods;authLevel. Determines what keys (authorisation), if any, need to be present on the request in order to invoke the function. The authorisation level can be one of the following values:- ANONYMOUS: No API key is required.
- FUNCTION: A function-specific API key is required. This is the default value if none is provided.
- ADMIN: The master key is required.
  • third is our function takes a query parameter (name) or body (with name in the JSON) and returns Welcome, with the value from the name. When a name is not supplied it returns a 404 error with a message.

Now, let’s test our function locally, deploy and make sure it works as expected.

To test locally (and when deployed), we will need to run the below two commands from the command line

  • gradle jar --info. This will build a jar file. The --info option gives us details of what happened as part of building the jar file;
  • gradle azureFunctionRun. This will enable us to test the HTTP trigger locally. This starts our function on http://localhost:7071/api/download
Running our function locally
  • Next, we will test our function to confirm it works.
Testing our function locally

Finally, we will deploy to the Azure platform and test. To do this we will run the below commands

  • az login. This is to make sure we are logged in to our Azure subscription;
  • gradle azureFunctionDeploy. This will use credentials az login to deploy our function. Once deployed this automatically creates
Resource group. Named with the resourceGroup we supplied.Storage account. This is required by Functions. The name is generated randomly based on Storage account name requirements.App Service plan. Serverless Consumption plan hosting for your function app in the specified region. The name is generated randomly.Function app. A function app is the deployment and execution unit for your functions. The name is your appName, appended with a randomly generated number.

Before we consider @BlobInput, we will need to create a container (more like a folder) in our storage account where our file will be stored.

Create a container in our storage account

I know… I know, you are asking how do I find out the name of the newly created storage account 😄

We have two options

  • Login to our Azure portal subscription; or
  • Use the Azure CLI which we installed as part of our pre-requisites. In this post, we will go for this option 🙄 🙄

Let’s begin…

  • First, we will need to fetch our function app settings. This will be downloaded and the local.settings.json file updated. We can do this by running
func azure functionapp fetch-app-settings FUNCTION_NAME --show-keys
  • Now we can open our local.settings.json file and our storage account can be found in the value for AccountName
Locating storage account name
  • Next, we execute the below command to create our container
az storage container create --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY -n java-functions-container -g eau-java-functions-rgwhere
AZURE_STORAGE_ACCOUNT - Is our storage account name saved in an environment variable
AZURE_STORAGE_KEY - Is our storage account key value saved in an environment variable. This is the value from AccountKey found in local.settings.json
  • Then we upload an image to our newly created java-functions-container container. We will use the Azure portal for this
Uploading a file to java-functions-container from Azure portal

Now, both HttpTrigger and BlobInput…

HttpTrigger and BlobInput

Now that we have our @BlobInput added to the mix, we will update by redeploying it.

To update our function, we only need to run gradle azureFunctionDeploy again.

Updating our function

Let us now confirm we can download/view our file.

  • We will need our function link, this can be viewed by running
func azure functionapp list-functions java-azure-function-5214 --show-keys
Details of our java-azure-function-5214 function
  • Using Postman, make a GET request

So far we have looked at

  • What Azure functions is and its basic concepts;
  • Developed a basic HttpTrigger function in our local environment and tested it locally too;
  • Deployed to Azure platform using the Gradle build tool;
  • Added a BlobInput binding to our HttpTrigger;
  • Updated our function on the Azure platform; and
  • Tested this to confirm we can view the file.

Thank you again for following through and as is the tradition the code for this post is available on GitHub.

Before we go it is worth cleaning up our resources so we do not incur any unplanned costs. This can be done by running the below command

az group delete --name eau-java-functions-rgThis will delete all resources created in our eau-java-functions-rg resource group which was used in this post.

Till then….

--

--

Etimbuk U
Etimbuk U

Written by Etimbuk U

Java | Flutter | Android | Some JavaScript | API Design

No responses yet